← Voltar ao curso
Nível 3 — Automação e escala · Módulo 3.2 — Hooks: da recomendação à garantia

3.2.4 · Exit codes e a pegadinha do 1

4 min de vídeo TEC BASIS

Objetivo: ao final, o consultor usa exit codes corretamente em hook simples, e nunca mais escreve exit 1 esperando que algo seja bloqueado.

O que você precisa levar desta aula

  1. 0 = sucesso, o stdout é lido como JSON. 2 = erro bloqueante, o stderr volta para o Claude como contexto. Qualquer outro valor, incluindo 1, não bloqueia. Isso contraria a convenção de shell de propósito.
  2. Stdout em texto puro vira contexto em exatamente três eventos: SessionStart, UserPromptSubmit e UserPromptExpansion. Nos outros, vai só para o log de depuração.
  3. Vários eventos ignoram bloqueio — entre eles o PostToolUse, porque a ferramenta já rodou. Se a regra precisa impedir, o evento é PreToolUse.

Os exit codes

Exit code Significado Bloqueia? O que acontece com a saída
0 Sucesso Não, por si só Stdout é interpretado como JSON (aula 3.2.3); em três eventos, texto puro vira contexto
2 Erro bloqueante Sim Stderr volta para o Claude como contexto, para ele se corrigir
1, 3, qualquer outro Erro não bloqueante Não Registrado; a execução continua

Exceção: WorktreeCreate bloqueia com qualquer valor diferente de zero.

Por que exit 1 engana

Em shell, exit 1 é o jeito universal de dizer "falhei". Todo mundo tem isso automatizado no dedo. Em hook do Claude Code, exit 1 significa "houve um erro, mas siga em frente".

O sintoma é traiçoeiro: o hook parece funcionar. Ele detecta o problema, escreve a mensagem, você vê a mensagem na tela — e a ação acontece de qualquer forma. Um hook de política escrito com exit 1 é uma garantia que existe só na aparência.

#!/usr/bin/env bash
# ATENÇÃO: em hook do Claude Code, só exit 2 bloqueia. exit 1 NÃO bloqueia.
if grep -qE 'MRDQAS|MRDPRD' <<< "$CLAUDE_TOOL_INPUT"; then
  echo "Regra Wayon: nenhuma ação automatizada em QAS ou PRD do MRD." >&2
  exit 2   # <-- 2, nunca 1
fi
exit 0

Vale manter esse comentário no topo de todo hook de bloqueio que a squad escrever. É a linha que evita o erro na revisão.

JSON ou exit code?

As duas formas funcionam. A escolha é de expressividade:

Exit code JSON no stdout
Complexidade Script de três linhas Precisa montar e imprimir JSON
O que consegue expressar Bloquear ou não bloquear allow / deny / ask / defer, razão, e reescrever a chamada
Quando usar Regra binária simples Quando precisa de razão estruturada, ou de updatedInput

Para política de ambiente — binária, sem nuance — exit code basta. Para redigir dado pessoal, só JSON serve, porque só ele tem updatedInput.

Os três eventos em que stdout vira contexto

Evento Para que serve
SessionStart Injetar contexto no início da sessão — e reinjetar depois de compactar (aula 3.2.5)
UserPromptSubmit Acrescentar contexto junto do prompt que o consultor acabou de enviar
UserPromptExpansion Acrescentar contexto na expansão de comando

Em todos os outros eventos, texto puro no stdout vai apenas para o log de depuração. É por isso que a aula 3.2.5 usa SessionStart e não PostCompact: o mecanismo de reinjeção depende deste comportamento.

Eventos que ignoram bloqueio

PostToolUse, PostToolUseFailure, PermissionDenied, Notification, MessageDisplay, SessionStart, SessionEnd, Setup, SubagentStart, StopFailure, InstructionsLoaded, PostCompact, CwdChanged, FileChanged, WorktreeRemove.

A lógica é sempre a mesma: ou a ação já aconteceu (PostToolUse, PermissionDenied), ou o evento é informativo (Notification, MessageDisplay). Em SessionStart, um exit 2 aparece como aviso, não como bloqueio.

Regra Wayon Todo hook de bloqueio da Wayon é revisado por um par antes de entrar em pasta de cliente, e a revisão tem um item obrigatório: confirmar que o caminho de bloqueio usa exit 2, não exit 1. Um hook de política com exit 1 passa em teste visual — a mensagem aparece — e falha em produção, porque a ação acontece do mesmo jeito.

📖 Exit codes e saída de hook

Quiz — 5 questões

1.Seu hook detecta um comando em QAS, escreve a razão no stderr e termina com exit 1. O que acontece com o comando?
  • a)É bloqueado, e a razão volta para o Claude

    Esse é o comportamento do exit 2. Com exit 1, a razão pode até aparecer, mas nada é bloqueado.

  • b)Executa normalmente — exit 1 não bloqueia

    Correto. É a pegadinha central da aula: o hook parece funcionar, e a ação acontece de qualquer forma.

  • c)É bloqueado, mas sem devolver a razão para o Claude

    Não há bloqueio nenhum com exit 1; a devolução da razão é característica do exit 2.

Ver resposta e por quê
a) Esse é o comportamento do exit 2. Com exit 1, a razão pode até aparecer, mas nada é bloqueado.
b) Correto. É a pegadinha central da aula: o hook parece funcionar, e a ação acontece de qualquer forma.
c) Não há bloqueio nenhum com exit 1; a devolução da razão é característica do exit 2.
2.Você quer que um hook bloqueie uma ação e que o Claude leia o motivo para se corrigir. Qual exit code?
  • a)0, com a razão no stdout

    exit 0 por si só não bloqueia; para negar com exit 0 seria preciso um JSON com permissionDecision: "deny" (aula 3.2.3), não texto solto.

  • b)1, com a razão no stderr

    exit 1 não bloqueia — a ação acontece mesmo com a mensagem aparecendo.

  • c)2, com a razão no stderr

    Correto. exit 2 bloqueia e o stderr volta para o Claude como contexto.

Ver resposta e por quê
a) exit 0 por si só não bloqueia; para negar com exit 0 seria preciso um JSON com permissionDecision: "deny" (aula 3.2.3), não texto solto.
b) exit 1 não bloqueia — a ação acontece mesmo com a mensagem aparecendo.
c) Correto. exit 2 bloqueia e o stderr volta para o Claude como contexto.
3.Um hook PostToolUse detecta que a edição feita violou a convenção de nomenclatura e sai com exit 2. O que acontece com a edição?
  • a)É revertida automaticamente, porque exit 2 bloqueia

    exit 2 bloqueia ação futura; não desfaz o que já foi executado — e PostToolUse está na lista de eventos que ignoram bloqueio.

  • b)Permanece — PostToolUse roda depois do sucesso da ferramenta e ignora bloqueio; para impedir seria preciso PreToolUse

    Correto. Quando PostToolUse dispara, a edição já existe.

  • c)É bloqueada antes de ser gravada em disco

    O evento dispara depois da execução bem-sucedida da ferramenta; não há o que impedir nesse ponto.

Ver resposta e por quê
a) exit 2 bloqueia ação futura; não desfaz o que já foi executado — e PostToolUse está na lista de eventos que ignoram bloqueio.
b) Correto. Quando PostToolUse dispara, a edição já existe.
c) O evento dispara depois da execução bem-sucedida da ferramenta; não há o que impedir nesse ponto.
4.Você escreveu um hook Stop que imprime, em texto puro no stdout, um resumo do que ficou pendente. O Claude não parece ver esse texto.
  • a)É bug — stdout de hook sempre vai para o contexto

    Não vai sempre: na maioria dos eventos, texto puro no stdout vai apenas para o log de depuração.

  • b)Falta terminar o hook com exit 2 para que o texto chegue ao Claude

    Em Stop, o caminho de devolver texto é o JSON (decision: "block" com additionalContext), não texto puro somado a exit code.

  • c)Texto puro no stdout só vira contexto em SessionStart, UserPromptSubmit e UserPromptExpansion — em Stop, é preciso usar JSON

    Correto. São exatamente três eventos com esse comportamento.

Ver resposta e por quê
a) Não vai sempre: na maioria dos eventos, texto puro no stdout vai apenas para o log de depuração.
b) Em Stop, o caminho de devolver texto é o JSON (decision: "block" com additionalContext), não texto puro somado a exit code.
c) Correto. São exatamente três eventos com esse comportamento.
5.Por que a convenção de exit code de hook é considerada uma armadilha, mesmo por gente experiente em shell? ---
  • a)Porque exit 0 significa falha, ao contrário do shell

    exit 0 significa sucesso nos dois — nisso a convenção coincide.

  • b)Porque em shell qualquer valor diferente de zero indica falha, mas em hook somente o 2 bloqueia

    Correto — e é justamente a experiência prévia em shell que faz a pessoa escrever exit 1 no automático.

  • c)Porque exit codes de hook mudam conforme o evento

    A convenção é estável entre eventos; a única exceção é WorktreeCreate, que bloqueia com qualquer valor diferente de zero.

Ver resposta e por quê
a) exit 0 significa sucesso nos dois — nisso a convenção coincide.
b) Correto — e é justamente a experiência prévia em shell que faz a pessoa escrever exit 1 no automático.
c) A convenção é estável entre eventos; a única exceção é WorktreeCreate, que bloqueia com qualquer valor diferente de zero.