Quatro prompts que você redigita viram quatro arquivos
As extensões do Claude Code são os arquivos que um repositório pode guardar para você parar de redigitar o mesmo prompt: uma skill, um hook, um subagent e um servidor MCP. Rodamos quatro tarefas recorrentes em um repo real, uma pequena API de faturas com um arquivo JSON como banco de dados. Cada tarefa recebeu primeiro a extensão errada, e a escolha errada falhou na tela todas as vezes. Depois cada uma virou um arquivo commitado. Esta página cobre os quatro gestos e o cartão de escolha que decide qual tipo usar.
Dois resultados enquadram tudo o que vem a seguir. Um hook costuma ser descrito como uma garantia e uma regra no arquivo de memória como um pedido, mas nosso primeiro hook rodou, exibiu o aviso e a escrita passou mesmo assim. O que transforma um hook que roda em um hook que bloqueia é explicado na seção do hook. O primeiro servidor de docs que conectamos não mudou nada, e o que respondeu foi o servidor de docs da própria ferramenta. A receita de hook que a maioria copia também exige um arquivo de script separado, mas a nossa cabe inteira no arquivo de configurações.
Antes de escrever qualquer um dos quatro arquivos, você precisa saber o que sua sessão já carrega.
O que são os quatro e o que sua sessão já carrega
Uma extensão do Claude Code é um arquivo que você adiciona ao repo para parar de redigitar as mesmas instruções na caixa de prompt. Existem quatro tipos, e quem já os conhece pode pular para a próxima seção.
- Uma skill é um roteiro arquivado no seu repo. O Claude carrega os passos só quando essa tarefa aparece, e você pode iniciá-la digitando o nome dela.
- Um hook é como um hook de commit no git, mas para as ações do próprio Claude. É um script que roda em um evento, como uma edição, seja qual for a intenção do Claude.
- Um subagent é um terceirizado com a mesa vazia. Ele recebe o seu briefing, faz a leitura e devolve um relatório em vez da pilha de arquivos.
- Um servidor MCP é um cliente de API que o Claude pode chamar, para dados que não estão no seu disco, como um banco de dados ou a documentação de uma ferramenta.
Para medir qualquer coisa, primeiro você precisa de uma linha de base. Abra uma sessão nova e rode o comando de contexto. Ele imprime uma tabela com tudo o que foi carregado antes da sua primeira palavra. No nosso teste, o total foi 26,2k tokens de uma janela de 1m, sem linha de arquivos de memória e sem nenhuma linha de MCP. Cada tarefa que você der ao Claude daqui para a frente ou acrescenta uma linha a essa tabela ou a deixa como está.
O primeiro job: o checklist de release vira uma skill
Uma skill é um checklist arquivado em uma pasta própria que o Claude só lê quando você a chama. O primeiro job é um checklist de release que você vive colando na caixa de prompt: typecheck, testes, subir a versão, adicionar uma linha no changelog, commit e tag.
O lugar óbvio para ele é o arquivo de memória, e essa é a escolha errada, porque tudo o que está nesse arquivo é carregado em cada turno de cada sessão. Colocamos os passos ali e lemos a tabela de contexto de novo. Ela tinha ganhado uma linha de Memory files com 210 tokens, carregada quer você esteja fazendo um release ou não. A documentação diz exatamente isso: quando uma seção desse arquivo virou um procedimento, o lugar dela é em uma skill.
A escolha certa é uma pasta dentro do diretório de skills com os passos em .claude/skills/release/SKILL.md, uma descrição curta no topo e uma linha extra de frontmatter, disable-model-invocation: true. Essa linha tira a skill da lista do próprio Claude, então só você pode iniciá-la, que é o que se quer para algo que faz commit e tag. Depois você digita uma barra e o nome dela, e o checklist roda.
| Leitura do contexto | Total | Linha de Memory files |
|---|---|---|
| Linha de base, sessão nova | 26.2k / 1m | nenhuma |
| Checklist no arquivo de memória | 26.4k / 1m | 210 tokens |
| Checklist como skill | 26.2k / 1m | nenhuma, skill não listada |
Quando rodamos, o Claude concluiu os seis passos em menos de um minuto: typecheck, 34 testes, versão de 0.4.0 para 0.4.1, linha no changelog, commit e tag. Ele também pegou uma falha no nosso próprio checklist. O comando de commit como estava escrito ignora arquivos não rastreados, então o Claude colocou o novo changelog no stage primeiro e avisou. O release agora é um comando que você digita, vive no repo para o time inteiro e não custa nada de contexto até você digitá-lo.
O segundo job: a regra que precisa valer vira um hook
Um hook é um script que o Claude Code roda antes de uma ferramenta disparar, aqui antes de qualquer edição ou escrita, e o que esse script responde decide o que acontece em seguida. O segundo job é uma regra que nunca pode quebrar: neste repo o arquivo de dados armazenados nunca é editado à mão, e toda mudança passa por uma migração.
Escrever essa regra em maiúsculas no arquivo de memória não a faz valer. A documentação é direta: esse arquivo é contexto, não configuração imposta, e uma instrução como "never edit this file" é um pedido, não uma garantia, enquanto um hook que bloqueia a edição é imposição.
O primeiro hook óbvio é o que escrevemos. No arquivo de configurações, ele vigia as escritas no arquivo de dados, exibe um aviso e sai com código zero, como um script de log. Em seguida pedimos ao Claude para mudar o valor de uma fatura direto no arquivo, sem migração. O hook rodou e o aviso apareceu, mas a escrita passou: o preço unitário da terceira fatura foi de 8000 para 12000. A resposta do Claude nunca mencionou o hook, porque um hook que sai limpo e não imprime nada não tomou nenhuma decisão, e os avisos dele nunca chegam ao Claude.
A garantia não é o hook rodar, é a resposta que ele devolve. A correção mantém o mesmo hook e faz com que ele devolva uma decisão de deny: uma linha curta de JSON com o motivo que você quer que o Claude leia. Sair com código dois bloqueia do mesmo jeito. O hook inteiro é um comando inline em .claude/settings.json, sem arquivo de script:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -c 'if (.tool_input.file_path // \"\") | test(\"data/.*\\\\.json$\") then {hookSpecificOutput: {hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: \"Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).\"}} else empty end'"
}
]
}
]
}
}
Com o mesmo pedido, a edição foi recusada e o arquivo de dados ficou intacto. O Claude viu este erro: PreToolUse:Edit hook error: Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions). Ele disse que não tinha mudado nada e perguntou se devia ignorar a proteção ou escrever a migração.
O limite, que o próprio Claude apontou: este hook vigia só as ferramentas de edição e escrita, e em uma rodada posterior o Claude se ofereceu para fazer a mudança pelo shell. Um hook precisa cobrir o shell também, ou o shell precisa ficar fora da sua lista de permissões, porque uma proteção que cobre só uma porta continua sendo um pedido. O hook não custa nada de contexto, porque roda fora da sua conversa.
O terceiro job: a auditoria que inunda vira um subagent
Um subagent é uma sessão separada do Claude com um contexto próprio, em branco. Ele recebe o seu briefing, lê o que precisa e só devolve um resumo. O terceiro job é uma auditoria que inunda a sua thread: conferir cada arquivo da pasta de código-fonte contra as convenções do README e listar toda violação com arquivo e linha.
Pedida inline, o Claude lê todos os arquivos para dentro da sua conversa, e todo esse conteúdo fica lá pelo resto da sessão. Na nossa rodada o Claude leu os 19 arquivos de código, a linha Messages da tabela de contexto inchou e os achados ficaram enterrados sob a pilha.
A escolha certa é um arquivo em .claude/agents/, aqui .claude/agents/conventions-auditor.md, com um nome, uma descrição de quando usar, as ferramentas que ele pode usar e um modelo fixado. Fixar o modelo importa, porque um subagent, se não, herda o que a sua sessão estiver usando, e a maioria dos trabalhos de leitura não precisa do modelo grande.
Fizemos exatamente a mesma pergunta de novo, e o Claude leu os arquivos ele mesmo. Nas duas tentativas ele nunca chamou o agente, mesmo ele estando listado: 0 de 2 delegações. O que funcionou foi citá-lo no começo do prompt: "use the conventions auditor subagent". Saiu uma única chamada e voltou um resumo curto.
| Execução | Contexto total | Linha Messages |
|---|---|---|
| Auditoria inline | 46.1k | 19.3k |
| Subagent nomeado | 34.1k | 7.2k |
Os achados foram igualmente bons: o subagent pegou um bug de caminho que a execução inline tinha deixado passar, e deixou passar um que a execução inline achou. Então o arquivo faz o subagent existir, e o seu prompt faz o Claude usá-lo; até o Claude escolhê-lo sozinho, você o cita. Um limite: a conta não mudou. As duas execuções custaram o mesmo, porque a leitura continua acontecendo, só que não na sua janela.
O quarto job: o dado que o Claude não alcança vira um servidor MCP
Um servidor MCP é um cliente de API que o Claude pode chamar, e o trabalho para o qual ele existe são dados mais novos que o modelo ou que ficam fora do seu disco. A escolha reflexa aqui é errada, e nós a fizemos primeiro.
Conectamos um servidor de docs público para a nossa biblioteca de testes e pedimos ao Claude um teste com fake timers. Nada mudou: o Claude já conhecia essa biblioteca e só chamou o servidor quando o citamos. Documentação que o modelo já conhece não é dado que ele não alcança.
O teste de verdade é uma pergunta sobre o próprio Claude Code, sobre uma configuração de hook mais nova que o modelo: qual configuração faz um hook com falha bloquear, qual versão a adicionou e qual é o JSON exato. Sem servidor, o Claude leu o schema de configurações da própria instalação, não achou nada e respondeu que não conseguiu encontrar a configuração e que não ia chutar uma. Essa resposta é honesta e inútil, porque a configuração existe; ela só chegou depois do conhecimento do modelo.
A correção é adicionar ao repo o servidor de docs do próprio Claude Code, com escopo de projeto, para que a config vá parar em um arquivo na raiz do repo que o time inteiro recebe:
claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcp
Isso grava .mcp.json. O Claude Code lista o servidor como pendente até você aprová-lo na sua próxima sessão, e a partir daí as ferramentas dele ficam disponíveis para o Claude. Fizemos a mesma pergunta em palavras simples, sem citar o servidor. O Claude pesquisou a documentação, achou a página e respondeu com a configuração, a versão e o JSON a adicionar, com o link.
A configuração é "onFailure": "block", que faz um hook com falha bloquear a ação em vez de deixá-la passar. Ela chegou na v2.1.295, enquanto a nossa instalação ainda estava na 2.1.294. Esse é o limite: o Claude nunca disse que a nossa instalação não podia usá-la, porque não conferiu a versão, então a versão você confere por conta própria. Na tabela de contexto o servidor aparece só pelos nomes das ferramentas, carregadas sob demanda até o Claude buscar uma.
Quatro arquivos, um cartão de escolha
Depois dos quatro jobs, o git status mostra exatamente quatro arquivos, porque o hook não precisa de um script próprio:
.claude/skills/release/SKILL.md.claude/settings.json.claude/agents/conventions-auditor.md.mcp.json
Faça o commit deles, e a próxima sessão neste repo terá todos, assim como qualquer pessoa que o clonar.
O motivo de cada escolha ter sido certa é uma tabela que a documentação traz, e que você pode guardar como cartão de decisão:
| Situação | Extensão |
|---|---|
| Um procedimento que você cola mais uma vez | Skill |
| Algo que precisa acontecer toda vez, sem perguntar | Hook |
| Uma tarefa lateral que inunda a conversa com uma saída que você não vai reler | Subagent |
| Dados que você vive copiando de um lugar que o Claude não enxerga | Servidor MCP |
Os tipos também se combinam. A documentação une uma skill a um servidor MCP: o servidor fornece a conexão, e a skill ensina o Claude a usá-la bem. Uma skill também pode rodar dentro de um subagent em fork, para que a leitura dela fique fora da sua janela. Quando outro repo precisar da mesma configuração, um plugin é a caixa que entrega todos juntos.
O cartão decide o tipo, não a qualidade. Um hook que só registra, um subagent que você nunca cita e um servidor para docs que o modelo já conhece estavam todos no tipo certo e mesmo assim falharam. Escolha o tipo pelo cartão e depois faça o arquivo responder, não apenas existir.
Veredito: quando usar cada um, quando nenhum, a regra única
Os quatro jobs que você costumava redigitar foram testados do jeito errado primeiro, falharam na tela e agora são arquivos no repo que funcionam. O release é um comando que você digita. A regra dos dados é um hook que recusa a edição e faz o Claude perguntar. A auditoria volta como um resumo. A pergunta sobre a documentação recebe a configuração e a versão que o Claude não conseguiu achar sozinho.
O hook com que abrimos, o que rodou e deixou a escrita passar, ensinou a regra por baixo de tudo isso: um hook decide só pelo que o comando dele devolve, então um hook que só registra é um pedido fantasiado.
Alguns casos não pedem nenhuma dessas opções:
- Uma convenção que o Claude insiste em errar ainda pertence ao arquivo de memória, como uma linha, não como um procedimento.
- Um trabalho que você precisa conduzir de um lado para o outro, com o seu histórico à vista, fica na thread principal e não em um subagent.
- Um servidor de docs para uma biblioteca que o modelo já conhece custa contexto e não muda nada. Conecte o que o modelo não pode ter, não o que ele já tem.
A regra para lembrar: dê a cada job que você redigita o seu próprio arquivo, escolha o tipo pelo cartão e saiba que, entre eles, só o hook nunca pergunta.
AIDive