Lição undefined de undefined: Receitas de hooks
Esta referência tem 6 hooks prontos pra adicionar a .claude/settings.json. É um menu pra consultar quando descobrir que está pedindo a mesma coisa repetidas vezes (a regra da 3ª repetição, M3/L11). Não instale tudo de uma vez.
Cada um vem com:
- Quando vale: situação concreta que justifica
- JSON pra colar
- Pegadinhas: dependências (jq, ripgrep), edge cases, quando NÃO usar
Pré-requisitos: jq instalado (brew install jq no macOS). Quase todos os hooks usam pra parsear o input. Se você não tem, instala antes de copiar.
1 — Stop: rodar lint sempre que código foi modificado
Quando vale: universal. Se você não rodar lint manual a cada sessão, instala isso.
Já está na L02. Referência aqui pra completude.
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "if git diff --quiet HEAD -- 'src/' 'app/' 2>/dev/null; then exit 0; else npm run check; fi"
}
]
}
]
}
}Pegadinhas:
- Se você comita frequentemente,
git diff HEADpode estar limpo enquanto sua sessão ainda mexeu; nesse caso o hook não roda. Não é problema (commit limpo = lint já rodou). - Se
npm run checkdemora muito (30s+), considera trocar pornpm run lintsó.
2 — PreToolUse: bloquear leitura de .env* reais
Quando vale: universal. Já está na L02; copia aqui de novo pra referência.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "INPUT=$(cat); FILE=$(echo \"$INPUT\" | jq -r '.tool_input.file_path // \"\"'); if echo \"$FILE\" | grep -qE '\\.env\\.(local|production|development)$|\\.env$' && ! echo \"$FILE\" | grep -qE '\\.env\\.example$'; then echo 'BLOCKED: Cannot read .env files (use .env.example instead).' >&2; exit 2; fi; exit 0"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r '.tool_input.command // \"\"'); if echo \"$CMD\" | grep -qE 'cat .*\\.env|head .*\\.env|less .*\\.env'; then echo 'BLOCKED: Cannot cat/head/less .env files.' >&2; exit 2; fi; exit 0"
}
]
}
]
}
}Pegadinhas:
- Se você tem nome
.env.testou similar, o regex pode não pegar; ajusta. - Se Claude lê
.env.example, tudo bem. Mas se você tem segredo nele (não deveria, mas às vezes acontece), o exemplo também precisa de proteção.
3 — PreToolUse: bloquear git push --force no main
Quando vale: se você trabalha sozinho em main, força push é tentação. Bloquear pra obrigar a virar branch + PR.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r '.tool_input.command // \"\"'); if echo \"$CMD\" | grep -qE 'git push.*--force.*main|git push.*-f.*main'; then echo 'BLOCKED: force push to main detected. Use a feature branch + PR.' >&2; exit 2; fi; exit 0"
}
]
}
]
}
}Pegadinhas:
- Bloqueia só
mainliteral. Se você usamasterouproductioncomo branch principal, ajusta o regex. - Não bloqueia
git push --force-with-lease(que é mais seguro). Se quer bloquear esse também, adiciona--force-with-leaseao regex.
4 — PostToolUse: rodar prettier após Edit/Write
Quando vale: se você se irrita com formatação inconsistente entre sessões. Faz Claude rodar prettier no arquivo modificado, automaticamente, após cada edição.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "INPUT=$(cat); FILE=$(echo \"$INPUT\" | jq -r '.tool_input.file_path // \"\"'); if echo \"$FILE\" | grep -qE '\\.(ts|tsx|js|jsx|json|md|mdx)$'; then npx prettier --write \"$FILE\" 2>/dev/null || true; fi; exit 0"
}
]
}
]
}
}Pegadinhas:
|| trueno fim garante que erro do prettier (arquivo não-formato suportado, config faltando) não trava o hook.- Se o projeto não tem prettier instalado,
npx prettierbaixa toda vez e fica lento. Confirma comnpx prettier --versionantes.
5 — Stop: alertar quando sessão modificou 10+ arquivos
Quando vale: sessão que mexeu muito merece review antes de commit. Hook lembra você.
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "COUNT=$(git diff --name-only HEAD 2>/dev/null | wc -l | tr -d ' '); if [ \"$COUNT\" -ge 10 ]; then echo \"⚠️ Sessão modificou $COUNT arquivos. Considera review antes de commit.\" >&2; fi; exit 0"
}
]
}
]
}
}Pegadinhas:
- Não bloqueia, só avisa. Exit 0.
- Conta arquivos modificados desde último commit, não da sessão exclusivamente. Pode dar falso positivo se você tinha mudanças no
git diffdesde antes.
6 — PreToolUse: forçar leitura de CLAUDE.md antes de qualquer Bash
Quando vale: projetos com convenções fortes onde você quer garantir que Claude leu o CLAUDE.md. Aviso: é um hook agressivo. Só use se a equipe pede consistência.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "if [ ! -f /tmp/claude-md-loaded-$$ ]; then if [ -f CLAUDE.md ]; then echo \"REMINDER: CLAUDE.md exists. If you haven't read it, do so before running tools.\" >&2; touch /tmp/claude-md-loaded-$$; fi; fi; exit 0"
}
]
}
]
}
}Pegadinhas:
- Avisa só 1x por sessão (graças ao
/tmp/claude-md-loaded-$$flag;$$é o PID da sessão). - Lembrete, não obrigação. Claude pode ignorar.
Padrões gerais que valem
- Sempre exit 0 no caminho feliz, exit 2 só pra bloquear. Outros exit codes têm comportamento undefined.
- Stderr (
>&2) é onde mensagens pro usuário vão. Stdout é o output do hook em si. - As condições devem ser baratas: um hook que demora 2s a cada Bash trava sua produtividade.
jqpra parsear input. Sem jq, hooks ficam quebradiços. Instala primeiro.- Hooks vão em
.claude/settings.json(versionado, time vê) OU.claude/settings.local.json(privado, só você). Hooks de segurança no versionado; hooks de preferência pessoal no local.
Quando NÃO escrever hook
A mesma regra da 3ª repetição (M3/L11) vale aqui. Se você teve uma sessão chata e pensou "deveria ter hook que faz X", anota em docs/maybe-hooks.md. Espera a 3ª vez que você lamentar. Aí escreve.
Hook prematuro tem custo: cada hook adiciona latência (mínima, mas existe) e cada Claude que ler o .claude/settings.json vai carregar a regra. 10 hooks sutis cumprem menos que 2 hooks centrais. Edita a lista, não acumula.