Claude Code

Rode seus próprios comandos automaticamente com os hooks do Claude Code

Hooks são comandos de shell que o Claude Code roda em momentos fixos, como antes de um comando ou depois de uma edição. Registre o que o Claude roda, bloqueie o que ele nunca deve rodar, confira cada arquivo que ele edita e receba um aviso quando ele estiver esperando por você.

Conferido na documentação oficial em

Nesta página9 seções
  1. 01O que é um hook
  2. 02Onde ficam os hooks
  3. 03Exemplo 1: registrar todos os comandos
  4. 04Exemplo 2: bloquear force push
  5. 05Exemplo 3: conferir a sintaxe do PHP depois de cada edição
  6. 06Exemplo 4: saber quando o Claude está esperando por você
  7. 07Os hooks rodam com as suas permissões
  8. 08Quando um hook não funciona
  9. 09Fontes

Você pode escrever no seu CLAUDE.md "sempre confira a sintaxe do PHP depois de editar um arquivo", e o Claude costuma fazer isso. Costuma. Um hook dá certeza. É um comando seu que o Claude Code roda toda vez que chega um determinado momento, não importa o que o Claude decida.

Este guia explica como os hooks funcionam e depois monta quatro, passo a passo: um registro de todos os comandos que o Claude roda, um bloqueio de force push, uma checagem de sintaxe depois de cada edição em PHP e um aviso quando o Claude está esperando por você. Os quatro foram testados em sessões reais.

O que é um hook

Um hook tem três partes:

  • Um evento: o momento em que ele roda. Por exemplo PreToolUse (antes de o Claude usar uma ferramenta), PostToolUse (depois que uma ferramenta roda sem erro) ou Notification (quando o Claude Code precisa da sua atenção).
  • Um filtro (matcher): os casos em que ele vale. Nos eventos de ferramentas, é o nome da ferramenta, como Bash ou Edit|Write.
  • Um comando: o que roda. Normalmente um comando de shell ou um script.

Quando o evento acontece, o Claude Code manda os detalhes para o seu comando em JSON, pela entrada padrão. Quando a ferramenta é o Bash, isso inclui o comando que o Claude quer rodar:

JSON
{
  "session_id": "abc123",
  "cwd": "/home/voce/projeto",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

O seu comando lê o que precisa (os exemplos aqui usam o jq), faz o trabalho dele e responde com o código de saída:

  • Código 0: nada contra. O Claude Code segue normalmente. No PreToolUse isso não aprova nada: as regras de permissão normais continuam valendo.
  • Código 2: um erro que bloqueia. O que o script escreve na saída de erros volta como motivo. Um hook PreToolUse bloqueia a chamada da ferramenta, e o Claude lê a sua mensagem. Um hook PostToolUse não consegue desfazer nada, porque a ferramenta já rodou, mas o Claude lê a mensagem mesmo assim e pode corrigir o problema.
  • Qualquer outro código: um erro que não bloqueia. A ação segue, e aparece um aviso curto hook error.

Os eventos que você vai usar mais:

  • PreToolUse: antes de uma chamada de ferramenta. O único desta lista que consegue parar uma chamada de ferramenta antes de ela rodar.
  • PostToolUse: depois que uma chamada de ferramenta roda sem erro.
  • Notification: quando o Claude Code está esperando a sua permissão ou uma resposta sua.
  • SessionStart: quando uma sessão começa ou é retomada. O texto simples que o comando imprimir entra no contexto do Claude.
  • UserPromptSubmit: quando você envia uma mensagem, antes de o Claude vê-la.
  • Stop: quando o Claude termina a resposta.

A lista completa, com mais de 30 eventos, está na referência dos hooks, nas fontes no final.

Onde ficam os hooks

Os hooks vão nos mesmos arquivos de configurações que as regras de permissão, dentro de uma chave hooks:

  • ~/.claude/settings.json: todos os projetos da sua máquina.
  • .claude/settings.json em um projeto: compartilhado com todo mundo, se você colocar no git.
  • .claude/settings.local.json em um projeto: só você.

Digite /hooks em uma sessão para ver todos os hooks em vigor, agrupados por evento, e o arquivo de onde vem cada um. O menu é só para consulta. Para adicionar ou mudar um hook, edite o arquivo de configurações, ou peça para o Claude fazer isso. Normalmente o Claude Code percebe a mudança sozinho, sem reiniciar.

Cada exemplo abaixo é um arquivo de configurações completo. Se o seu já tiver uma chave hooks, junte o novo evento aos que já estão lá, em vez de substituir o bloco inteiro.

Exemplo 1: registrar todos os comandos

Este hook adiciona a um arquivo do projeto cada comando de shell que o Claude roda:

.claude/settings.json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.command' >> \"$CLAUDE_PROJECT_DIR\"/.claude/command-log.txt"
          }
        ]
      }
    ]
  }
}

O Claude Code define $CLAUDE_PROJECT_DIR como a pasta do projeto, então o registro sempre vai para o mesmo lugar, não importa onde o Claude esteja trabalhando.

Nota

O PostToolUse só roda depois que uma chamada dá certo. No nosso teste, um comando bloqueado por outro hook nunca chegou ao registro. Para registrar também as chamadas que falham, adicione o mesmo hook em PostToolUseFailure.

Exemplo 2: bloquear force push

Um hook PreToolUse roda antes da verificação de permissões, então ele consegue parar um comando mesmo em modos que deixariam o comando rodar.

  1. Escreva o script

    .claude/hooks/block-force-push.sh
    #!/bin/bash
    # Block force pushes. Claude sees the message and can choose another way.
    command=$(jq -r '.tool_input.command')
    
    if [[ "$command" =~ git[[:space:]]+push.*(--force|[[:space:]]-f([[:space:]]|$)) ]]; then
      echo "Blocked by hook: force push is not allowed in this project. Push normally or ask the user." >&2
      exit 2
    fi
    exit 0
  2. Deixe o script executável

    Terminal · seu usuário
    chmod +x .claude/hooks/block-force-push.sh
  3. Teste na mão

    Mande para ele o mesmo tipo de JSON que o Claude Code mandaria e confira o código de saída:

    Terminal · seu usuário
    echo '{"tool_input":{"command":"git push --force origin main"}}' | .claude/hooks/block-force-push.sh; echo "exit=$?"

    Saída

    Blocked by hook: force push is not allowed in this project. Push normally or ask the user.
    exit=2

    Um git push origin main normal deve mostrar só exit=0.

  4. Registre o hook

    .claude/settings.json
    {
      "hooks": {
        "PreToolUse": [
          {
            "matcher": "Bash",
            "hooks": [
              {
                "type": "command",
                "if": "Bash(git push *)",
                "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-force-push.sh"
              }
            ]
          }
        ]
      }
    }

O campo if usa os mesmos padrões das regras de permissão. Com ele, o script só roda para comandos git push, em vez de rodar para todo comando de shell.

Quando o Claude tentou git push --force origin master no nosso teste, o comando não rodou, e o Claude recebeu:

Saída

PreToolUse:Bash hook error: ["$CLAUDE_PROJECT_DIR"/.claude/hooks/block-force-push.sh]: Blocked by hook: force push is not allowed in this project. Push normally or ask the user.

Um hook que bloqueia funciona em todos os modos de permissão, inclusive no modo Ignorar permissões (bypassPermissions). O contrário não vale: um hook que responde "allow" não passa por cima de uma regra de bloqueio.

Atenção

Assim como uma regra de permissão de Bash, este script só lê o texto do comando. Um comando escrito de um jeito diferente pode escapar de um padrão. Use hooks como este para pegar erros. Para um limite que precisa valer de verdade, junte uma regra de bloqueio com a sandbox (/sandbox).

Exemplo 3: conferir a sintaxe do PHP depois de cada edição

Este hook roda php -l em cada arquivo PHP que o Claude cria ou edita. Se a checagem falhar, o Claude lê o erro e corrige o arquivo na hora.

  1. Escreva o script

    .claude/hooks/php-lint.sh
    #!/bin/bash
    # After Claude edits a PHP file, check its syntax and report errors back to Claude.
    file=$(jq -r '.tool_input.file_path')
    
    if ! output=$(php -l "$file" 2>&1); then
      echo "PHP syntax check failed for $file:" >&2
      echo "$output" >&2
      exit 2
    fi
    exit 0
  2. Deixe o script executável

    Terminal · seu usuário
    chmod +x .claude/hooks/php-lint.sh
  3. Registre o hook para o Write e para o Edit

    .claude/settings.json
    {
      "hooks": {
        "PostToolUse": [
          {
            "matcher": "Edit|Write",
            "hooks": [
              {
                "type": "command",
                "if": "Write(*.php)",
                "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/php-lint.sh"
              },
              {
                "type": "command",
                "if": "Edit(*.php)",
                "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/php-lint.sh"
              }
            ]
          }
        ]
      }
    }

Atenção

Uma condição if vale para uma ferramenta só. A nossa primeira versão tinha só Edit(*.php) e, quando o Claude criou um arquivo novo com a ferramenta Write, a checagem nunca rodou. O log de depuração dizia Skipping hook due to if condition "Edit(*.php)" not matching. O Claude cria arquivos novos com o Write e altera os que já existem com o Edit, então você precisa de um de cada.

Com os dois no lugar, pedimos ao Claude para criar um arquivo sem um ponto e vírgula. O hook respondeu:

Saída

PHP syntax check failed for /home/voce/projeto/hello.php:
PHP Parse error:  syntax error, unexpected end of file, expecting "," or ";" in /home/voce/projeto/hello.php on line 1
Errors parsing /home/voce/projeto/hello.php

O Claude adicionou o ponto e vírgula com a ferramenta Edit, o hook rodou de novo sem dizer nada, e o arquivo passou.

O mesmo esquema serve para qualquer verificador ou formatador: python -m py_compile, shellcheck, prettier --write e assim por diante.

Exemplo 4: saber quando o Claude está esperando por você

Quando o Claude precisa da sua permissão e você está em outra janela, um aviso poupa você de olhar o terminal a cada poucos minutos. Coloque este hook nas suas configurações de usuário, para funcionar em todos os projetos.

Em um desktop Linux:

~/.claude/settings.json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
          }
        ]
      }
    ]
  }
}

Em um Mac, use este comando no lugar:

Texto
osascript -e 'display notification "Claude Code needs your attention" with title "Claude Code"'
  • permission_prompt bate quando um pedido de permissão já está esperando há uns seis segundos.
  • idle_prompt bate quando o Claude terminou de responder há mais ou menos um minuto e você ainda não digitou nada.
  • Com um filtro vazio, "", o hook roda em todos os tipos de aviso.

Nota

Testamos o evento em um servidor sem interface gráfica, então o hook de teste escreveu em um arquivo em vez de mostrar um aviso. Ele rodou enquanto o pedido de permissão estava esperando, e o JSON que recebeu trazia a mensagem Claude needs your permission. Os comandos notify-send e osascript são os da documentação oficial. O notify-send precisa de uma sessão gráfica, então não faz nada por SSH. No Mac, o primeiro aviso pode exigir que você libere as notificações do Editor de Scripts nos Ajustes do Sistema.

Os hooks rodam com as suas permissões

Um hook de comando roda como você, com acesso a tudo o que a sua conta de usuário alcança. Antes de adicionar um, leia o que ele faz.

Os hooks do .claude/settings.json de um projeto vêm de quem escreveu o repositório, e duas coisas dos nossos testes são importantes:

  • Em uma sessão interativa, o Claude Code não roda os hooks até você confiar na pasta. Mas a caixa de confiança que vimos mostrava a regra de permissão já aprovada da pasta e não dizia nada sobre os hooks. Antes de confiar em um repositório que não foi você que escreveu, abra o .claude/settings.json dele e leia você mesmo a seção hooks.
  • O claude -p nunca mostra a caixa de confiança e trata a pasta como confiável para os hooks. No nosso teste, ele ignorou a regra de liberar do projeto porque a pasta não era confiável, mas rodou os hooks do projeto mesmo assim. Para rodar o claude -p no repositório de outra pessoa sem os hooks dela, adicione --setting-sources user. Assim o Claude Code lê só as suas configurações, e os hooks e as regras do projeto ficam de fora. Testamos: com essa opção, o hook de registro do projeto não rodou.

Quando um hook não funciona

  • Teste o script na mão primeiro, com uma linha de JSON, como no exemplo 2. Confira o código de saída com echo $?.
  • Deixe os scripts executáveis com chmod +x. Sem isso, o script não roda.
  • Confira o filtro. Os filtros diferenciam maiúsculas de minúsculas: bash não é Bash.
  • jq: command not found: instale o jq, ou escreva o hook em Python.
  • Veja o que aconteceu. Aperte Ctrl+O para abrir a transcrição, onde aparecem as mensagens de bloqueio e os erros dos hooks. Um hook que roda sem erro não mostra nada ali.
  • Leia o log de depuração. Inicie o Claude Code com claude --debug-file /tmp/claude.log, e o log mostra quais hooks bateram, quais foram pulados e por quê, e o que cada um imprimiu.

Fontes

Conferido em

Claude Code 2.1.280 no AlmaLinux 9.8, com jq 1.6 e PHP 8. Os quatro hooks foram testados em um projeto git descartável: os exemplos 1 a 3 em execuções com claude -p, e o exemplo 4 e a caixa de confiança em uma sessão interativa. As mensagens acima foram copiadas dessas sessões, com o caminho do projeto trocado por /home/voce/projeto.