Reagir a eventos com um mod
Manipule eventos do Claude Code a partir de um mod: observe, reescreva ou responda chamadas de ferramentas, prompts e turnos, filtre quais eventos um hook manipula e planeje para outros mods.
Um hook é um manipulador de eventos: uma função que Claude Code executa quando um evento nomeado acontece. Claude Code dispara um evento em cada ponto onde está prestes a agir, como quando executa uma ferramenta, envia um prompt, faz uma solicitação ao modelo ou inicia ou encerra uma sessão. Seu hook é executado antes de Claude Code agir, portanto pode observar o evento, reescrevê-lo ou respondê-lo no lugar de Claude Code. Você registra um hook com on(eventName, handler).
Construa seu primeiro mod antes de começar aqui. Para cada evento e seus campos exatos, consulte a referência ou leia os tipos para sua compilação.
Como um hook manipula um evento
Um hook fica entre um evento e o que Claude Code faria sobre ele, portanto pode observar o evento, reescrevê-lo ou respondê-lo por si mesmo. Ele recebe três argumentos: a API de mods como $, o evento como e e o próximo manipulador como next. Os manipuladores de um evento formam uma cadeia de middleware. next(e) chama o próximo manipulador, que é o hook de outro mod ou, no final da cadeia, o comportamento próprio de Claude Code, e é resolvido para o resultado. O que seu hook faz com next decide qual dos três ele faz.
Observe um evento
Para observar um evento sem alterá-lo, faça seu trabalho e retorne next(e). Este hook registra cada ferramenta que Claude está prestes a usar:
on('tool.call', async ($, e, next) => {
// Executa antes da ferramenta
$.ui.log('Claude is about to use ' + e.tool)
// Passe o evento adiante inalterado
return next(e)
})
Antes de cada ferramenta ser executada, uma linha fraca como ● my-mod: Claude is about to use Bash aparece na transcrição, onde my-mod é o nome do seu plugin. A ferramenta é executada como seria sem o mod.
Para agir após o evento, await next(e), faça seu trabalho e retorne o resultado. Este hook registra cada ferramenta após sua execução:
on('tool.call', async ($, e, next) => {
// Deixe a ferramenta ser executada e aguarde seu resultado
const result = await next(e)
// Executa após a ferramenta
$.ui.log(e.tool + ' finished')
// Devolva o resultado inalterado
return result
})
A linha agora aparece após cada ferramenta terminar. Claude lê o mesmo resultado de qualquer forma, porque o hook retorna o que next(e) foi resolvido.
Reescreva um evento
Para alterar o que Claude Code age, como o texto de um prompt, chame next com uma cópia modificada do evento. O evento em si é imutável: é congelado em cada profundidade e atribuir a um campo lança um erro. Este hook corta cada prompt antes de ser enviado:
on('prompt.submit', async ($, e, next) => {
// Passe uma cópia do evento com seu texto alterado
return next({ ...e, text: e.text.trim() })
})
Manipuladores posteriores e Claude Code recebem o prompt cortado e nunca veem o original. Você também pode alterar o resultado: await next(e), depois retorne uma cópia do resultado com um campo substituído.
Responda a um evento
Para manipular um evento você mesmo, retorne um resultado sem chamar next. Isso interrompe a cadeia, portanto mods posteriores e o comportamento próprio de Claude Code não são executados. Este hook recusa cada comando Bash:
on('tool.call', { tool: 'Bash' }, async () => {
// Nenhuma chamada para next, portanto o comando nunca é executado
return { deny: 'Bash is turned off in this project. Use the file tools.' }
})
Quando Claude tenta um comando Bash, o comando não é executado e Claude lê o texto deny como o resultado da ferramenta. Cada evento tem sua própria forma de resultado, que a referência de eventos lista.
Filtre quais eventos um hook manipula
Para executar um hook apenas para alguns eventos, passe um filtro como o segundo argumento para on. Claude Code chama o filtro de matcher. É um objeto cujos campos são comparados com os do evento, e o hook é executado apenas quando cada campo corresponde. Um campo pode ser um valor, uma matriz de valores permitidos ou uma expressão regular.
Cada linha neste exemplo registra a mesma função, hook, para um conjunto mais estreito de chamadas de ferramentas:
// Uma string corresponde a um valor: apenas chamadas Bash
on('tool.call', { tool: 'Bash' }, hook)
// Uma matriz corresponde a qualquer valor nela: chamadas Edit e Write
on('tool.call', { tool: ['Edit', 'Write'] }, hook)
// Uma expressão regular corresponde por padrão: cada ferramenta de um servidor MCP
on('tool.call', { tool: /^mcp__github__/ }, hook)
hook é executado uma vez para uma chamada Bash, Edit ou Write, e uma vez para uma chamada a uma ferramenta cujo nome começa com mcp__github__. Uma chamada para qualquer outra ferramenta, como Read, não corresponde a nenhuma das três, portanto hook não é executado para ela.
O nome do evento pode ser um wildcard. 'classic.*' corresponde a cada evento de hook de configurações. '*' corresponde a cada evento exceto os eventos de telemetria, que você conecta por nome ou como 'telemetry.*'.
Registre cada evento uma vez por matcher. Se você chamar on duas vezes para session.start sem um matcher, o módulo falhará ao carregar com on("session.start") is registered twice without a matcher. Coloque tudo o que seu mod faz no início da sessão em um hook.
Hook o que Claude está fazendo
Conecte esses eventos para ver ou alterar uma chamada de ferramenta, um prompt ou um turno conforme acontece. Para cada evento e o que um hook pode retornar, consulte a referência de eventos.
Guarde ou altere uma chamada de ferramenta
Um hook tool.call vê cada ferramenta que Claude está prestes a usar, portanto pode recusar a chamada, alterar seus argumentos ou deixá-la passar. tool.call dispara quando Claude Code está prestes a executar uma ferramenta, incluindo chamadas que um subagenteaz e chamadas para ferramentas MCP. e.tool é o nome da ferramenta e os argumentos da ferramenta são campos de e, como e.command para Bash. Quando você chama next(e), Claude Code executa a verificação de permissão e depois a ferramenta.
Este hook recusa um comando Bash que força um push e diz a Claude por quê:
// O matcher limita o hook a chamadas Bash, portanto e.command é o comando do shell
on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
if (/git push .*--force/.test(e.command)) {
// Retornar sem chamar next responde ao evento, portanto o comando nunca é executado
return { deny: 'Force pushes are not allowed in this repository. Push to a new branch instead.' }
}
// Cada outro comando passa para a verificação de permissão e depois para Bash
return next(e)
})
Quando Claude tenta git push --force, o comando não é executado e nenhum prompt de permissão aparece, porque o hook nunca chama next. Claude lê o texto deny como o resultado da ferramenta, portanto escreva-o como uma instrução que Claude pode agir. Cada outro comando Bash é executado como seria sem o mod.
Para agir após uma ferramenta ter sido executada, await next(e), faça seu trabalho e retorne o que next lhe deu. Este hook registra cada arquivo .mdx que Claude altera, com $.ui.log, que adiciona uma linha fraca à transcrição que Claude não lê:
on('tool.call', { tool: ['Edit', 'Write'] }, async ($, e, next) => {
// Aguarde a verificação de permissão e a ferramenta, e mantenha o que produziram
const result = await next(e)
// Uma chamada recusada volta como { deny }, e uma falhada tem isError definido
const changed = !result.deny && !result.isError
if (changed && e.file_path.endsWith('.mdx')) $.ui.log('Claude changed ' + e.file_path)
// Retorne o resultado como veio, portanto Claude lê o que a ferramenta retornou
return result
})
Depois que Claude edita ou escreve um arquivo .mdx, uma linha fraca na transcrição nomeia o arquivo. Nada é registrado para outro tipo de arquivo ou para uma chamada que foi recusada ou falhou. A visualização de Claude da chamada não muda, porque o hook retorna o resultado que recebeu.
Para alterar uma chamada, passe argumentos alterados para next. Para tentar novamente uma chamada, chame next(e) novamente: um hook que vê isError no primeiro resultado pode executar a ferramenta uma segunda vez e retornar esse resultado. Para responder a uma chamada você mesmo, retorne um objeto com um campo result, como { result: 'Skipped by my-mod' }, sem chamar next. Quando você faz isso, nenhum prompt de permissão aparece e a ferramenta não é executada, portanto o resultado que você retorna é tudo que Claude aprende sobre o que aconteceu.
Hooks nas configurações gerenciadas de sua organização são executados antes de qualquer hook tool.call de mod, e um bloqueio de um deles é final.
Mantenha uma chamada de ferramenta até o usuário decidir
Um hook pode pausar uma chamada de ferramenta e perguntar ao usuário o que fazer antes de prosseguir. Um hook tool.call pode await antes de chamar next ou retornar, e a chamada de ferramenta permanece pendente até então. Para fazer a pergunta ao usuário, chame $.ui.ask. Ele mostra sua pergunta acima de uma lista numerada de suas opções, no diálogo que Claude usa para lhe fazer uma pergunta, e é resolvido para o rótulo que o usuário escolhe. Após suas opções, o diálogo adiciona uma linha para digitar uma resposta diferente e uma linha Chat about this.
O padrão RISKY neste exemplo corresponde a rm -r, rm -rf, git reset --hard e git push com --force, e perde outras grafias como git push -f. Este módulo pergunta antes de executar um comando Bash que corresponde ao padrão:
const RISKY = /\brm\s+-rf?\b|\bgit\s+reset\s+--hard\b|\bgit\s+push\b.*--force/
export function register(on) {
on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
// Deixe cada outro comando passar sem uma pergunta
if (!RISKY.test(e.command)) return next(e)
// Comece a partir da resposta segura, portanto uma pergunta que ninguém responde recusa o comando
let answer = 'Refuse'
try {
// A chamada de ferramenta aguarda aqui até o usuário escolher um dos dois rótulos
answer = await $.ui.ask('Run this command? ' + e.command, ['Run it', 'Refuse'])
} catch {
// O usuário descartou a pergunta ou esta é uma execução claude -p sem ninguém para perguntar
}
if (answer !== 'Run it') {
// Responda sem chamar next, portanto o comando não é executado
return { deny: 'The user declined this command. Ask before trying a different approach.' }
}
return next(e)
})
}
Quando Claude tenta um comando como rm -rf build, a pergunta aparece com o comando nela, e o comando aguarda a resposta:
- O usuário escolhe Run it: o hook chama
next(e)e a verificação de permissão usual ainda é executada após ele - O usuário escolhe Refuse: o comando não é executado e Claude lê o texto
deny - O usuário digita uma resposta:
$.ui.aské resolvido para o texto digitado. O hook o compara comRun it, portanto qualquer outro texto recusa o comando. - Ninguém responde:
$.ui.askrejeita quando o usuário descarta a pergunta ou escolhe Chat about this, e em uma execuçãoclaude -p, portanto o blococatchdeixa a resposta emRefuse
Mantenha a espera dentro de uma chamada de API de mods como $.ui.ask, porque esse tempo não conta contra o limite de tempo de 10 segundos do hook. O tempo gasto aguardando uma promessa sua conta. Claude Code pula um hook que expira, portanto o comando mantido seria executado.
Reescreva ou adicione a um prompt
Um hook prompt.submit vê cada prompt antes do turno começar, portanto pode reescrever o texto ou adicionar a ele. e.text é o que foi digitado.
| Para fazer isso | Retorne isto |
|---|---|
| Reescreva o prompt. A mensagem na transcrição mostra o novo texto. | next({ ...e, text: newText }) |
| Adicione texto apenas que Claude lê, após o prompt | next({ ...e, context: [...(e.context ?? []), extraText] }) |
| Impeça que o prompt seja enviado | { drop: 'the reason' } |
Este hook adiciona o nome da ramificação atual para Claude sempre que um prompt menciona uma solicitação de pull:
on('prompt.submit', async ($, e, next) => {
// Passe um prompt que não menciona uma solicitação de pull como está
if (!/\bPR\b|pull request/i.test(e.text)) return next(e)
const git = await $.process.run(['git', 'branch', '--show-current'])
// Fora de um repositório git o comando falha, portanto não há ramificação para adicionar
if (git.exitCode !== 0) return next(e)
// Mantenha qualquer contexto que um hook anterior adicionou e adicione mais uma linha para Claude
return next({ ...e, context: [...(e.context ?? []), 'Current branch: ' + git.stdout.trim()] })
})
Quando você envia um prompt como open a PR for this change, sua mensagem parece a mesma na transcrição e Claude também lê uma linha como Current branch: feature/auth após ela. Um prompt que não menciona uma solicitação de pull passa inalterado e git não é executado.
Outros eventos cobrem o resto do que Claude lê: prompt.section para cada seção do prompt do sistema, prompt.context para o contexto enviado com a primeira mensagem e skill.prompt para o texto de uma skill. Texto desses hooks que muda entre solicitações invalida o cache de prompt.
Siga um turno
Um turno é tudo o que Claude faz em resposta a um prompt. Conecte turn.start, turn.step e turn.complete para seguir um:
| Evento | Quando dispara | O que um hook pode fazer |
|---|---|---|
turn.start |
Um turno começa | Observe. e.turnId identifica o turno nos outros dois eventos. |
turn.step |
Claude Code está prestes a enviar uma solicitação ao modelo. Um turno com chamadas de ferramentas tem várias. e.agentId é definido para uma solicitação de um subagenteaz. |
Leia o uso de token de cada solicitação, envie-o para um modelo diferente com next({ ...e, model }) ou responda sem chamar o modelo |
turn.complete |
O turno terminou, incluindo um turno que o usuário interrompeu, onde e.isAborted é true. e.answer é o texto final de Claude, e.durationMs quanto tempo levou e e.usage os totais de token do turno. Um turno de um subagenteaz dispara com e.agentId definido. |
Observe ou retorne um objeto com um campo text, como { text: 'Done in 12 seconds' }, para mostrar uma linha sob a resposta |
Escreva um hook turn.step como um gerador assíncrono, porque o evento flui. yield* next(e) encaminha a resposta conforme flui e é avaliado para o resultado terminado. Este hook registra quanto de cada solicitação a API Claude serviu do cache de prompt:
// function* torna o hook um gerador, que pode passar a resposta adiante pedaço por pedaço
on('turn.step', async function* ($, e, next) {
// Envie a solicitação, encaminhe cada pedaço conforme chega e mantenha o resultado terminado
const result = yield* next(e)
// Pule um resultado que não relata contagens de token
if (result.usage) {
$.ui.log('cache read ' + result.usage.cache_read_input_tokens + ' · wrote ' + result.usage.cache_creation_input_tokens)
}
// Retorne o resultado inalterado, portanto o turno continua como usual
return result
})
A resposta de Claude flui para a tela como faria sem o mod. Após cada solicitação terminar, uma linha fraca na transcrição fornece o número de tokens lidos do cache e o número escrito nele. Um turno com chamadas de ferramentas tem várias solicitações, portanto adiciona várias linhas.
result.usage contém as quatro contagens de token que a API Claude relata para uma solicitação, mais o model que respondeu: input_tokens, output_tokens, cache_read_input_tokens e cache_creation_input_tokens. O hook é executado para solicitações de subagenteaz também, portanto verifique e.agentId quando você quer apenas a conversa principal.
Hook os eventos de hook de configurações
Hooks de configurações são os hooks de comando, HTTP, prompt e agente que você configura em arquivos de configurações. Cada evento de hook de configurações, como Stop, SessionEnd ou PostToolUse, também é um evento nomeado classic. seguido pelo nome do evento de hook de configurações, como classic.Stop. e é o JSON que um hook de configurações recebe em stdin, incluindo transcript_path.
Este hook usa Stop, que dispara quando Claude termina de responder, para registrar onde a transcrição da sessão é salva:
on('classic.Stop', async ($, e, next) => {
// e tem os mesmos campos que um hook Stop em um arquivo de configurações lê de stdin
$.ui.log('Transcript saved at ' + e.transcript_path)
// Passe o evento adiante, portanto hooks Stop em seus arquivos de configurações ainda são executados
return next(e)
})
Cada vez que Claude termina de responder, uma linha fraca na transcrição fornece o caminho do arquivo de transcrição. O hook retorna next(e), portanto observa o evento e não muda nada sobre como o turno termina.
Execute ao lado de outros mods
Vários mods podem conectar o mesmo evento e qualquer um deles pode falhar. Se seu mod bloqueia chamadas de ferramentas, verifique sua posição na cadeia e o que acontece quando seu hook falha.
A ordem em que os mods são executados
Hooks no mesmo evento formam uma cadeia de middleware. Cada next de um mod chama o hook do mod seguinte, e o último next atinge o comportamento próprio de Claude Code. O primeiro mod é o mais externo: vê o evento antes dos outros e o resultado após eles, e decide se os outros são executados. Um mod posterior não pode impedir que um anterior veja um evento.
Claude Code ordena a cadeia por onde cada mod vem:
- O guard integrado
sec-default@builtin, um mod integrado em Claude Code que/pluginlista comocc-plugin-sec-default, onde ele carrega, mods que sua organização lista emprependPluginse depois qualquer outro mod que conta como de sua organização e não está emappendPlugins - Mods que você instala
- Mods que sua organização lista em
appendPlugins - Outros mods integrados em Claude Code
Entre os mods que você instala, um mod é executado antes dos mods que lista em dependencies em seu manifesto. Dentro de um módulo, hooks são executados na ordem em que register chamou on.
Onde hooks de configurações são executados na ordem
Os hooks PreToolUse configurados em arquivos de configurações também são executados durante uma chamada de ferramenta, em pontos fixos na cadeia de mods:
- Hooks
PreToolUsede configurações gerenciadas: são executados antes do hooktool.calldo primeiro mod, e um bloqueio de um deles é final, portanto nenhum mod vê a chamada. - Hooks
PreToolUsede cada outro arquivo de configurações e dehooks/hooks.jsonde plugins: são executados após o último mod chamarnext, como parte do comportamento próprio de Claude Code. Um mod que respondetool.callsem chamarnextos impede de serem executados, e um mod que chamanextvê sua decisão no resultado que retorna.
tool.check é o evento onde Claude Code decide se uma chamada de ferramenta pode ser executada. Dispara após esses hooks e as regras de permissão terem decidido, e next(e) é resolvido para sua decisão. Um hook em tool.check pode retornar uma decisão diferente, como { decision: 'allow' }, portanto pode aprovar uma chamada que um hook no segundo grupo bloqueou. Estenda permissões com hooks lista quais decisões prevalecem sobre um mod.
Manipule um hook que falha
Um hook que falha não quebra a sessão e você pode decidir o que acontece em seu lugar. Quando um hook sem um manipulador .catch lança, expira ou retorna um resultado da forma errada, o que acontece a seguir depende se ele tinha chamado next:
- Falhou antes de chamar
next: Claude Code o pula e o próximo manipulador é executado em seu lugar - Falhou após
nextser resolvido: esse resultado permanece e nada é executado uma segunda vez
Uma linha nomeia o mod, o evento e o motivo, como my-mod: tool.call hook skipped: threw Error: boom. Onde você o lê depende da sessão, como Descubra por que um mod não faz nada lista. Um hook ui.render cujo desenho não valida é relatado diferentemente, como Construa uma árvore a partir de elementos descreve.
Para fazer um hook que bloqueia chamadas falhar fechado, adicione um manipulador de erro .catch que responda em seu lugar. Aqui, guard é sua função de hook:
// on retorna um registro e .catch anexa um manipulador a esse hook
on('tool.call', { tool: 'Bash' }, guard).catch(async ($, e, next) => {
// next.error.kind é 'throw' ou 'timeout', que diz como guard falhou
return { deny: 'The command guard failed, so this command was not run: ' + next.error.kind }
})
Enquanto guard funciona, o manipulador nunca é executado. Quando guard lança ou expira em uma chamada Bash, Claude Code chama o manipulador com o mesmo evento. O manipulador retorna { deny }, portanto o comando não é executado e Claude lê o texto com throw ou timeout no final. Sem o manipulador, Claude Code pularia guard e executaria o comando. O manipulador tem um segundo para responder.
Próximos passos
- Use a API de mods: adicione comandos e ferramentas, chame um modelo e execute trabalho em um temporizador
- Desenhe na interface: mostre o que seus hooks coletam em um painel ou acima do prompt
- Teste um mod: levante qualquer um desses eventos de um teste
- Referência de mods: cada evento, cada método de API de mods e os limites