Pular para o conteúdo

Builder OS

Builder · OS
apx · Receitas de hooks
FIM DO MÓDULO
~11 MIN DE LEITURA

Lição undefined de undefined: Receitas de hooks

lição undefined/undefined do Módulo 5

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 HEAD pode 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 check demora muito (30s+), considera trocar por npm run lint só.

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.test ou 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ó main literal. Se você usa master ou production como 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-lease ao 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:

  • || true no 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 prettier baixa toda vez e fica lento. Confirma com npx prettier --version antes.

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 diff desde 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

  1. Sempre exit 0 no caminho feliz, exit 2 só pra bloquear. Outros exit codes têm comportamento undefined.
  2. Stderr (>&2) é onde mensagens pro usuário vão. Stdout é o output do hook em si.
  3. As condições devem ser baratas: um hook que demora 2s a cada Bash trava sua produtividade.
  4. jq pra parsear input. Sem jq, hooks ficam quebradiços. Instala primeiro.
  5. 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.