O hook Stop do Claude Code executa um comando toda vez que o agente principal do Claude termina de responder, o que o torna o lugar certo para enviar uma notificação de "pronto", registrar o resultado ou rodar verificações. Ele não roda quando você interrompe o Claude, e um turno que termina em erro de API dispara StopFailure no lugar. O hook recebe a mensagem final do Claude em last_assistant_message e pode retornar decision: "block" com um reason para fazer o Claude continuar em vez de parar.
01Quando o Stop dispara e quando não dispara
O Stop dispara ao fim de cada turno, não só quando uma tarefa inteira é concluída. O Claude Code não tem noção de "a tarefa"; ele só sabe que o Claude parou de responder e está esperando por você. Uma pergunta curta gera um Stop, e um refactor de duas horas também.
Ele não dispara:
- quando você aperta Esc ou interrompe o turno de outra forma;
- quando o turno termina por causa de um erro de API, como rate limit, sobrecarga ou falha de autenticação. Nesses casos, o Claude Code dispara
StopFailure, com um campoerrorque você pode usar para filtrar (rate_limit,overloaded,server_errore outros); - quando um subagente termina. Para isso existe o
SubagentStop.
O Stop não suporta matchers, então qualquer matcher que você adicionar é ignorado e o hook roda em todo turno.
02O Stop hook mais simples
Adicione isto ao ~/.claude/settings.json para rodar um comando sempre que o Claude terminar. Este toca um som no macOS:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
]
}
}
Para comandos de notificação no desktop em Mac, Windows e Linux, veja como receber uma notificação quando o Claude Code terminar. O resto deste guia trata do que você pode fazer com o input e o output do hook.
03O que o hook recebe
Além dos campos comuns (session_id, transcript_path, cwd, permission_mode, hook_event_name), um hook Stop recebe:
last_assistant_messageO texto da resposta final do Claude neste turnostop_hook_activetrue se o Claude já está continuando porque um Stop hook bloqueoubackground_tasksTarefas ainda em execução, como shells em segundo plano ou subagentessession_cronsDespertares agendados nesta sessão, como os do /loopUse last_assistant_message em vez de ler o arquivo de transcript. O transcript é gravado de forma assíncrona e pode ainda não conter a mensagem final quando o hook roda.
background_tasks e session_crons indicam se a sessão terminou de verdade ou só está pausada até que um trabalho em segundo plano a acorde de novo. Um script de notificação pode verificar se ambos estão vazios antes de dizer "terminou".
04Coloque a última mensagem do Claude na notificação
Este script (macOS e Linux, requer jq) mostra o início da resposta do Claude e fica em silêncio se ainda houver trabalho em segundo plano rodando:
#!/bin/bash
# ~/.claude/hooks/notify-done.sh
input=$(cat)
# Skip if background tasks are still in flight
busy=$(jq '(.background_tasks // []) | length' <<<"$input")
[ "$busy" -gt 0 ] && exit 0
msg=$(jq -r '(.last_assistant_message // "Claude has finished")
| gsub("\\s+"; " ") | .[0:140]' <<<"$input")
if command -v osascript >/dev/null 2>&1; then
osascript -e 'on run argv' \
-e 'display notification (item 1 of argv) with title "Claude has finished"' \
-e 'end run' "$msg"
elif command -v notify-send >/dev/null 2>&1; then
notify-send "Claude has finished" "$msg"
fi
exit 0
Salve o arquivo, rode chmod +x ~/.claude/hooks/notify-done.sh e aponte o hook para ele:
{
"hooks": {
"Stop": [
{
"hooks": [{ "type": "command", "command": "~/.claude/hooks/notify-done.sh" }]
}
]
}
}
O script sempre sai com 0. Isso é importante: no Stop, o exit code 2 significa "não pare", então um script de notificação que saísse com 2 por engano manteria o Claude trabalhando.
05Fazendo o Claude continuar
O Stop é um dos eventos que podem bloquear. Bloquear uma parada significa que o Claude continua o turno, usando o seu motivo como próxima instrução. Você pode fazer isso de duas formas:
- saia com
2, e o Claude recebe o seu stderr como motivo; ou - saia com
0e imprima um JSON:
{
"decision": "block",
"reason": "The test suite is failing. Run npm test, fix the failures, then finish."
}
Se o hook está dando orientações em vez de reportar um erro, hookSpecificOutput.additionalContext também mantém a conversa rodando, mas o transcript marca isso como feedback do Stop hook em vez de erro de hook:
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Please run the test suite before finishing"
}
}
Evitando um loop infinito
Um Stop hook que sempre bloqueia nunca deixaria o Claude parar. Duas coisas protegem você:
stop_hook_activeficatruequando o Claude já está continuando por causa de um Stop hook. Verifique esse campo e saia cedo.- O Claude Code encerra o turno de qualquer forma depois que os Stop hooks bloquearem oito vezes seguidas sem nenhuma chamada de ferramenta entre elas. A variável de ambiente
CLAUDE_CODE_STOP_HOOK_BLOCK_CAPaumenta esse limite.
Aqui está um hook que faz o Claude corrigir testes com falha uma vez e depois o deixa parar:
#!/bin/bash
# ~/.claude/hooks/tests-must-pass.sh
input=$(cat)
[ "$(jq -r '.stop_hook_active' <<<"$input")" = "true" ] && exit 0
if ! npm test --silent >/dev/null 2>&1; then
jq -nc '{decision: "block", reason: "npm test is failing. Fix the failures, then finish."}'
fi
exit 0
Os hooks rodam no diretório de trabalho da sessão, então npm test roda no seu projeto. Mantenha as verificações rápidas: por padrão, um hook de comando pode rodar por até 600 segundos, e o Claude espera por ele.
Se você só quer que o Claude continue trabalhando em direção a um objetivo sem escrever um script, o comando nativo /goal do Claude Code configura um Stop hook com escopo de sessão para você.
06Notificações para falhas também
Como erros de API pulam o Stop, um alerta de "concluído" não vai avisar que o Claude desistiu por causa de um rate limit. Adicione o mesmo script em StopFailure se quiser ser avisado disso. A saída e o exit code dele são ignorados, então ele só pode notificar ou registrar log. No StopFailure, last_assistant_message contém o texto do erro, como "API Error: Rate limit reached".
07Onde o Claude Code Notifier se encaixa
Um Stop hook que chama um serviço de push é uma ótima forma de saber que o Claude terminou enquanto você está longe. Se quiser fazer isso por conta própria, o guia do ntfy é um bom começo.
O Claude Code Notifier usa o mesmo hook Stop, com uma diferença: ele pode enviar algo de volta. A ferramenta de linha de comando gratuita dele, ccn, roda no Mac, Windows e Linux e mostra uma notificação no desktop por conta própria. Com o app de iPhone ou Android pareado, você recebe um push "Claude terminou" assim que um turno acaba e pode digitar a próxima instrução no celular. O ccn a retorna pelo hook Stop como a próxima instrução do Claude, então a sessão continua sem você no teclado. Se você digitou nos últimos 30 segundos, nada fica esperando o celular. Ele é independente e não é feito pela Anthropic. Veja como se afastar durante tarefas longas para esse fluxo e a página do ccn para instalar.
08Perguntas frequentes
O Stop hook roda quando o Claude termina a tarefa inteira ou depois de cada resposta?
Depois de cada resposta. O Stop dispara sempre que o agente principal termina de responder, seja qual for o tamanho do pedido. Confira background_tasks e session_crons no input se precisar saber se ainda há trabalho agendado.
Por que meu Stop hook não disparou?
Talvez você tenha interrompido o turno, o que pula o Stop, ou ele terminou com um erro de API, que dispara StopFailure no lugar. Caso contrário, confira se /hooks lista seu hook e se o script é executável. O checklist de solução de problemas cobre o resto.
Por que o Claude continua trabalhando depois que meu Stop hook roda?
Seu hook está bloqueando a parada, seja saindo com código 2 ou imprimindo decision: "block". Garanta que um hook só de notificação sempre saia com 0 e que qualquer hook que bloqueie verifique stop_hook_active antes.
Como pego a resposta final do Claude no hook?
Leia last_assistant_message do JSON no stdin, por exemplo com jq -r '.last_assistant_message'. Não dependa de transcript_path: o arquivo pode ainda não conter a última mensagem quando o Stop dispara.
Qual a diferença entre Stop e SubagentStop?
O Stop é para o agente principal. O SubagentStop dispara quando um subagente termina, faz match pelo tipo de agente e suporta os mesmos campos de bloqueio. Um alerta "Claude terminou" deve ficar no Stop.