Verificação automática de código com Hooks: qualidade garantida ao escrever

Use PostToolUse Hooks para acionar lint automaticamente após cada escrita do Claude. O código de saída 2 devolve os erros ao Claude para correção automática.


O Claude Code escreve código rapidamente, mas nem sempre lembra de rodar o lint. Você pede para ele "verificar enquanto escreve" e às vezes ele faz, às vezes esquece. Os Hooks resolvem esse problema: transferem a responsabilidade da verificação de código do Claude para o sistema.

Este artigo foca em uma única coisa: usar PostToolUse Hooks para disparar verificações automaticamente após o Claude escrever código, garantindo que cada alteração de arquivo atenda aos seus padrões.


A abordagem: PostToolUse + filtro por tipo de arquivo

A lógica de um Hook de verificação de código é simples:

  1. Claude chama Write ou Edit para escrever um arquivo
  2. O Hook intercepta esse evento e obtém o caminho do arquivo
  3. Com base na extensão, decide qual ferramenta de verificação executar
  4. Verificação aprovada: código de saída 0, Claude continua
  5. Verificação falhou: código de saída 2, erros são enviados ao Claude para correção

O código de saída 2 é fundamental: faz o Claude receber o erro e corrigir automaticamente, formando um ciclo "escrever→verificar→corrigir".


Configuração básica: projetos de linguagem única

Ruby (RuboCop)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.rb ]] && rubocop -A \"$file\" --no-color -q || true'"
          }
        ]
      }
    ]
  }
}

JavaScript/TypeScript (ESLint)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" =~ \\.(js|ts|jsx|tsx)$ ]] && npx eslint --fix \"$file\" || true'"
          }
        ]
      }
    ]
  }
}

Python (ruff)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.py ]] && ruff check --fix \"$file\" && ruff format \"$file\" || true'"
          }
        ]
      }
    ]
  }
}

Configuração avançada: projetos multilinguagem

Projetos reais costumam misturar linguagens. Mover a lógica de verificação para um script independente é mais fácil de manter do que acumular comandos no JSON.

Criar .claude/hooks/lint.sh:

#!/bin/bash
set -e

input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path // empty')

[[ -z "$file" ]] && exit 0
[[ ! -f "$file" ]] && exit 0

ext="${file##*.}"

case "$ext" in
  rb)    rubocop -A "$file" --no-color -q ;;
  js|ts|jsx|tsx) npx eslint --fix "$file" --quiet ;;
  py)    ruff check --fix "$file"; ruff format "$file" ;;
  go)    gofmt -w "$file"; golangci-lint run "$file" 2>&1 ;;
  *)     exit 0 ;;
esac

Referenciar o script no settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [{ "type": "command", "command": "bash .claude/hooks/lint.sh" }]
      }
    ]
  }
}

Fazer o Claude corrigir automaticamente

Código de saída 2 + stderr é o que ativa a autocorreção do Claude:

run_lint() {
  local output exit_code
  output=$(rubocop "$file" --no-color 2>&1)
  exit_code=$?
  if [[ $exit_code -ne 0 ]]; then
    echo "$output" >&2
    exit 2
  fi
  rubocop -A "$file" --no-color -q
}

Excluir arquivos que não precisam ser verificados

skip_patterns=("vendor/" "node_modules/" "db/schema.rb" ".min.js" "_test.go")
for pattern in "${skip_patterns[@]}"; do
  [[ "$file" == *"$pattern"* ]] && exit 0
done

Resultado na prática

Com essa configuração, o fluxo de trabalho fica assim:

  1. Você pede ao Claude que escreva um arquivo Ruby
  2. Claude escreve com a ferramenta Write
  3. O Hook roda rubocop -A automaticamente — encontra 3 problemas
  4. RuboCop corrige 2 automaticamente; o 3º exige julgamento humano
  5. O 3º erro é enviado ao Claude via stderr
  6. Claude corrige o código, o Hook dispara novamente
  7. Segunda verificação: aprovada

Você nunca precisa rodar comandos de verificação manualmente.


Global vs. nível de projeto

Global (~/.claude/settings.json): Quando você usa as mesmas regras em todos os projetos.

Nível de projeto (.claude/settings.json no git): Recomendado para equipes. Qualquer pessoa que abrir o repositório recebe a configuração correta automaticamente.

Os dois coexistem; os hooks de projeto se mesclam com os globais e ambos são executados.


Resumo

A verificação automática de código com Hooks se resume a três passos:

  1. PostToolUse + matcher "Write|Edit" para interceptar escritas de arquivos
  2. Obter o caminho do arquivo e encaminhar ao linter correto pela extensão
  3. Em caso de falha, sair com código 2 para o Claude ver o erro e corrigir

Comece com uma linguagem. Quando funcionar, adicione mais. Faça commit dos scripts em .claude/hooks/ para compartilhar com a equipe.