SpyBara
Go Premium

plugins/mods/events.md 2026-10-31 23:02 UTC to 2026-11-01 23:57 UTC

No changes for this document in this history range.

2026
Mon 9 23:02 Tue 10 21:57

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 são registrados pelo próprio nome e com um filtro { to: 'collector' }.

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.

Fazer hook no que o Claude está fazendo

Trate estes eventos para ver ou alterar uma chamada de ferramenta, um prompt, um turno ou uma leitura de configurações enquanto acontecem. Para cada evento e o que um hook pode retornar, consulte a referência de eventos.

Proteger ou alterar uma chamada de ferramenta

Um hook tool.call vê cada ferramenta que o Claude está prestes a usar, então pode recusar a chamada, alterar seus argumentos ou deixá-la passar. tool.call é disparado quando o Claude Code está prestes a executar uma ferramenta, incluindo chamadas que um subagente faz e chamadas a 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), o Claude Code executa a verificação de permissão e depois a ferramenta.

Este hook recusa um comando Bash que faz force push e informa ao Claude o motivo:

// The matcher limits the hook to Bash calls, so e.command is the shell command
on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
  if (/git push .*--force/.test(e.command)) {
    // Returning without calling next answers the event, so the command never runs
    return { deny: 'Force pushes are not allowed in this repository. Push to a new branch instead.' }
  }
  // Every other command goes on to the permission check and then to Bash
  return next(e)
})

Quando o Claude tenta git push --force, o comando não é executado e nenhum prompt de permissão aparece, porque o hook nunca chama next. O Claude lê o texto de deny como o resultado da ferramenta, então escreva-o como uma instrução sobre a qual o Claude possa agir. Todos os outros comandos Bash são executados como seriam sem o mod.

Se este hook lançar um erro ou exceder o tempo limite, o Claude Code o ignora e o comando prossegue como prosseguiria sem o mod. Para recusar o comando nesse caso, adicione um handler .catch ao hook.

Para agir depois que uma ferramenta foi executada, use await next(e), faça seu trabalho e retorne o que next lhe deu. Este hook registra em log cada arquivo .mdx que o Claude altera, com $.ui.log, que adiciona uma linha esmaecida à transcrição que o Claude não lê:

on('tool.call', { tool: ['Edit', 'Write'] }, async ($, e, next) => {
  // Wait for the permission check and the tool, and keep what they produced
  const result = await next(e)
  // A refused call comes back as { deny }, and a failed one has isError set
  const changed = !result.deny && !result.isError
  if (changed && e.file_path.endsWith('.mdx')) $.ui.log('Claude changed ' + e.file_path)
  // Return the result as it came, so Claude reads what the tool returned
  return result
})

Depois que o Claude edita ou escreve um arquivo .mdx, uma linha esmaecida na transcrição indica o nome do arquivo. Nada é registrado em log para outro tipo de arquivo, nem para uma chamada que foi recusada ou falhou. A visão do Claude sobre a chamada não muda, porque o hook retorna o resultado que recebeu.

Seu hook também pode alterar uma chamada, tentar novamente, respondê-la ele mesmo ou reter seu resultado:

  • Alterar a chamada: passe argumentos alterados para next.
  • Tentar novamente a chamada: chame next(e) outra vez. Um hook que vê isError no primeiro resultado pode executar a ferramenta uma segunda vez e retornar esse resultado.
  • Responder você mesmo à chamada: retorne um objeto com um campo result, sem chamar next, e, para uma ferramenta integrada, dê a result o formato que o próprio resultado dessa ferramenta tem nos tipos do seu build. Nenhum prompt de permissão aparece e a ferramenta não é executada, então o resultado que você retorna é tudo o que o Claude fica sabendo sobre o que aconteceu.
  • Reter o resultado do Claude: retorne { deny: reason } depois de await next(e). O Claude lê o seu motivo no lugar do que next retornou. Quando a ferramenta foi executada, o deny impede que o Claude veja seu resultado e não desfaz nada do que a ferramenta fez. Quando a ferramenta foi executada com sucesso, o motivo vem depois de uma nota como Bash ran, and a plugin withheld its result:.

Os hooks nas configurações gerenciadas da sua organização são executados antes do hook tool.call de qualquer mod. Quando um hook gerenciado bloqueia a chamada, nenhum hook tool.call de mod é executado para essa chamada.

Reter 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 que ela prossiga. Um hook tool.call pode usar await antes de chamar next ou retornar, e a chamada de ferramenta permanece pendente até lá. Para fazer a pergunta ao usuário, chame $.ui.ask. Ele mostra sua pergunta acima de uma lista numerada das suas opções, no diálogo que o Claude usa para lhe perguntar algo, e resolve para o rótulo que o usuário escolher. Depois das 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 não detecta outras grafias como git push -f. Este módulo pergunta antes de executar um comando Bash que corresponda 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) => {
    // Let every other command through without a question
    if (!RISKY.test(e.command)) return next(e)
    // Start from the safe answer, so a question nobody answers refuses the command
    let answer = 'Refuse'
    try {
      // The tool call waits here until the user picks one of the two labels
      answer = await $.ui.ask('Run this command? ' + e.command, ['Run it', 'Refuse'])
    } catch {
      // The user dismissed the question, or this is a claude -p run with nobody to ask
    }
    if (answer !== 'Run it') {
      // Answer without calling next, so the command doesn't run
      return { deny: 'The user declined this command. Ask before trying a different approach.' }
    }
    return next(e)
  })
}

Quando o 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 habitual ainda é executada depois disso
  • O usuário escolhe Refuse: o comando não é executado, e o Claude lê o texto de deny
  • O usuário digita uma resposta: $.ui.ask resolve para o texto digitado. O hook o compara com Run it, então qualquer outro texto recusa o comando.
  • Ninguém responde: $.ui.ask é rejeitado quando o usuário dispensa a pergunta ou escolhe Chat about this, e em uma execução claude -p, então o bloco catch mantém a resposta como Refuse

Mantenha a espera dentro de uma chamada da API de mods, como $.ui.ask, porque esse tempo não conta para o limite de tempo do hook. O tempo gasto aguardando uma promise sua conta.

O Claude Code ignora um hook que excede o tempo limite, então o comando retido seria executado. Para recusá-lo nesse caso, encadeie um handler .catch ao registro que on retorna. Esse handler é separado do bloco catch dentro do hook.

Aprovar ou recusar uma chamada de ferramenta antes de o usuário ser consultado

Para decidir se uma chamada de ferramenta pode ser executada, trate tool.check, o evento em que o Claude Code toma essa decisão.

Para um comando ou caminho fixo, use uma regra de permissão como Bash(npm test), que não exige código. Trate tool.check quando a decisão depender do que é verdade naquele momento, como o branch Git atual ou um valor que outro hook registrou.

  • O que retornar: para decidir a chamada você mesmo, retorne um objeto seu, como { decision: 'allow' } ou { decision: 'deny', reason }. Para não interferir na decisão, retorne o que await next(e) lhe dá.
  • O que await next(e) lhe dá: a própria decisão do Claude Code, como um objeto cujo decision é allow, ask ou deny. Nenhum prompt de permissão aparece enquanto next(e) é executado.
  • Argumentos: e.input contém os argumentos da ferramenta, como command para Bash
  • Quando tool.check é disparado: depois que os hooks PreToolUse são executados, e antes do prompt de permissão, do classificador do modo auto ou de um hook PermissionRequest

Este hook recusa git push enquanto o branch atual for main:

on('tool.check', { tool: 'Bash' }, async ($, e, next) => {
  // Claude Code's own decision
  const decided = await next(e)
  if (!e.input.command.includes('git push')) return decided
  const branch = await $.process.run(['git', 'branch', '--show-current'])
  if (branch.stdout.trim() !== 'main') return decided
  return { decision: 'deny', reason: 'Push from a branch other than main' }
})

O hook nega git push em main e deixa passar todas as outras chamadas:

  • Em main: o hook retorna deny, mesmo quando uma regra permite git push
  • Em outro branch, e para outros comandos: a chamada recebe a decisão que receberia sem o mod
  • O hook lança um erro ou excede o tempo limite: a chamada também recebe a decisão que receberia sem o mod. Para recusar a chamada nesse caso, adicione um handler .catch que retorne { decision: 'deny', reason: 'the reason' }.

O hook compara o texto do comando, então trate-o como um lembrete para o Claude. Para bloquear pushes para main para todos, proteja o branch no seu host Git.

[!NOTE]

Um hook tool.check também pode retornar allow ou ask. No modo de planejamento, para uma chamada que não é somente leitura, como a edição de um arquivo, seu hook pode tornar mais restritiva a decisão que next(e) retorna, mas não pode torná-la mais permissiva. Para as regras de permissão e as decisões de hooks PreToolUse que o allow do seu hook não pode substituir, consulte Estender permissões com hooks.

Verificar se uma chamada de ferramenta é somente leitura

Em um hook tool.check, e.isReadOnly é true quando a ferramenta informa a chamada como somente leitura. Caso contrário, o campo está ausente, e não false. e.isReadOnly requer o Claude Code v2.1.304 ou posterior.

Algumas ferramentas, como Read, Grep, Glob e WebSearch, informam toda chamada como somente leitura. Bash e ferramentas MCP informam uma chamada como somente leitura nestes casos:

O valor é o que a própria ferramenta informa, então não aprove uma chamada com base apenas neste campo. Por exemplo, chamadas de WebFetch e Agent são informadas como somente leitura, embora WebFetch faça uma requisição a uma URL e Agent inicie um subagente que executa suas próprias ferramentas.

Este hook retorna um ask para toda chamada mcp__github__ que não é somente leitura e que ainda não foi negada:

on('tool.check', { tool: /^mcp__github__/ }, async ($, e, next) => {
  const decided = await next(e)
  // Read-only calls and denied calls pass through unchanged
  if (e.isReadOnly || decided.decision === 'deny') return decided
  return { decision: 'ask', reason: 'This tool can change the GitHub repository' }
})

Filtrar um hook tool.check pelo fato de a chamada ser somente leitura

Para limitar quais chamadas seu hook trata com base em e.isReadOnly, use o matcher para chamadas somente leitura e o corpo do hook para as demais:

  • Apenas chamadas somente leitura: adicione isReadOnly: true ao matcher, como em on('tool.check', { tool: 'Bash', isReadOnly: true }, hook)
  • Chamadas que não são somente leitura: teste e.isReadOnly no corpo do hook, como faz o exemplo mcp__github__. Se você colocar isReadOnly: false em um matcher, seu hook nunca é executado.

Reescrever ou complementar um prompt

Um hook prompt.submit vê cada prompt antes de o turno começar, então pode reescrever o texto ou complementá-lo. e.text é o que foi digitado.

Para fazer isto Retorne isto
Reescrever o prompt. A transcrição e o seu histórico de prompts mostram o novo texto. next({ ...e, text: newText })
Adicionar texto que só o Claude lê, depois do prompt next({ ...e, context: [...(e.context ?? []), extraText] })
Impedir que o prompt seja enviado { drop: 'the reason' }

Este hook adiciona o nome do branch atual para o Claude sempre que um prompt menciona um pull request:

on('prompt.submit', async ($, e, next) => {
  // Pass on a prompt that doesn't mention a pull request as it is
  if (!/\bPR\b|pull request/i.test(e.text)) return next(e)
  const git = await $.process.run(['git', 'branch', '--show-current'])
  // Outside a git repository the command fails, so there's no branch to add
  if (git.exitCode !== 0) return next(e)
  // Keep any context an earlier hook added, and add one more line for 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 aparece igual na transcrição, e o Claude também lê uma linha como Current branch: feature/auth depois dela. Um prompt que não menciona um pull request passa sem alterações, e git não é executado.

Para impedir um prompt, retorne { drop: 'the reason' } sem chamar next. O texto volta para a entrada de prompt do usuário, e ele vê Prompt dropped by a hook: seguido do seu motivo, então dirija o motivo a ele. O prompt ainda é enviado nestes casos:

  • Seu hook retorna um drop depois que sua chamada next(e) deixou o prompt passar: o turno ainda é executado, e o hook falha com uma mensagem que inclui a drop after its next() was answered
  • Seu hook lança um erro ou excede o tempo limite: o Claude Code o ignora e envia o prompt. Adicione um handler .catch para que uma falha não envie um prompt que o hook teria impedido.

Outros eventos cobrem o restante do que o Claude lê: prompt.section para cada seção do system prompt, 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 requisições invalida o cache de prompt. Em um hook skill.prompt, você também pode ler de qual plugin ou arquivo a skill veio.

Acompanhar um turno

Um turno é tudo o que o Claude faz em resposta a um prompt. Trate turn.start, turn.step e turn.complete para acompanhar um:

Evento Quando é disparado O que um hook pode fazer
turn.start Um turno começa Observar. e.turnId identifica o turno nos outros dois eventos.
turn.step O Claude Code está prestes a enviar uma requisição ao modelo. Um turno com chamadas de ferramenta tem várias. e.agentId é definido para a requisição de um subagente. Ler o uso de tokens de cada requisição, enviá-la para um modelo diferente com next({ ...e, model }) ou responder sem chamar o modelo
turn.complete O turno terminou, incluindo um turno que o usuário interrompeu, em que e.isAborted é true. e.answer é o texto final do Claude, e.durationMs quanto tempo levou e e.usage os totais de tokens do turno. O turno de um subagente o dispara com e.agentId definido. Observar, ou retornar um objeto com um campo text, como { text: 'Done in 12 seconds' }, para mostrar uma linha abaixo da resposta

Escreva um hook turn.step como um gerador assíncrono, porque o evento é transmitido em streaming. yield* next(e) encaminha a resposta à medida que ela é transmitida e resulta no resultado final. Este hook registra em log quanto de cada requisição a API do Claude atendeu a partir do cache de prompt:

// function* makes the hook a generator, which can pass the response on piece by piece
on('turn.step', async function* ($, e, next) {
  // Send the request, forward each piece as it arrives, and keep the finished result
  const result = yield* next(e)
  // Skip a result that reports no token counts
  if (result.usage) {
    $.ui.log('cache read ' + result.usage.cache_read_input_tokens + ' · wrote ' + result.usage.cache_creation_input_tokens)
  }
  // Return the result unchanged, so the turn continues as usual
  return result
})

A resposta do Claude é transmitida para a tela como acontece sem o mod. Depois que cada requisição termina, uma linha esmaecida na transcrição informa o número de tokens lidos do cache e o número gravado nele. Um turno com chamadas de ferramenta tem várias requisições, então adiciona várias linhas.

result.usage contém as contagens de tokens que a API do Claude informa para uma requisição, mais o model que respondeu: input_tokens, output_tokens, cache_read_input_tokens e cache_creation_input_tokens. O hook também é executado para as requisições de subagentes, então verifique e.agentId quando quiser apenas a conversa principal.

Para ver as chamadas de ferramenta que a própria API executou durante a requisição, como chamadas à ferramenta advisor, leia result.serverToolUses. O Claude Code não executa essas chamadas, então nenhum hook tool.call ou tool.check é disparado para elas. O campo está ausente quando a resposta não tem chamadas desse tipo, e requer o Claude Code v2.1.290 ou posterior.

Enviar uma requisição para um modelo diferente

Se uma requisição turn.step falhar com um erro de modelo quando model for um alias como haiku, atualize para o Claude Code v2.1.309 ou posterior, ou use um id de modelo completo. Antes da v2.1.309, um alias era enviado como escrito.

Para enviar uma requisição para um modelo diferente, passe para next o evento com model alterado, como em yield* next({ ...e, model: 'haiku' }).

Alterar ou recusar uma leitura de configurações

Use um hook settings.read para alterar ou recusar uma leitura de configurações, seja a de um mod ou a do próprio Claude Code:

  • A leitura de um mod: um mod chama $.settings.read.
  • A leitura do próprio Claude Code: seu hook é executado pela primeira vez quando o Claude Code mescla as configurações da sessão, depois que os mods da sessão foram carregados. Seu hook é executado novamente quando as configurações ou os plugins carregados mudam.

Verifique quais mods recebem este evento e a sua versão do Claude Code:

  • A guarda integrada: com configurações gerenciadas, ou com um /login Team ou Enterprise como credencial da sessão, a guarda integrada pode ignorar este hook em um mod que você instala ou carrega com --plugin-dir. Para verificar, procure no log de depuração por settings.read bypassed by cc-plugin-sec-default. Se uma linha com esse texto citar o seu mod, peça ao administrador da sua organização para instalar o mod como um dos mods da organização.
  • A versão: o disparo do evento na leitura do próprio Claude Code, e o campo e.layers, requerem o Claude Code v2.1.304 ou posterior.

O que um hook settings.read recebe

Campo Tipo O que contém
e.layers array Um objeto { source, settings } para cada fonte em uso que contém configurações, da menor precedência para a maior: plugin, user, project, local, flag, policy. Vazio em claude plugin test.
e.source string A única fonte que um mod solicitou, como em $.settings.read({ source: 'policy' }). e.layers então contém apenas essa camada. Ausente na leitura do próprio Claude Code e em qualquer outra leitura das configurações mescladas.

Em e.layers, plugin contém as configurações que os plugins habilitados fornecem, e é apenas um nome de camada, não um source que você possa passar para $.settings.read. flag contém as configurações passadas com --settings, e policy contém as configurações gerenciadas.

Distinguir a leitura do próprio Claude Code da leitura de um mod

Verifique next.origin:

  • A leitura do próprio Claude Code: { plugin: 'engine', tier: 'core' }
  • A leitura de um mod: plugin é o nome do mod que fez a chamada

O que um hook settings.read pode retornar

Retorne o resultado de next(e) sem alterações para deixar a leitura como está, ou retorne um destes:

Retorno Na leitura do próprio Claude Code Na leitura de um mod
{ value } Para cada chave alterada, você substitui o valor inteiro dela nas configurações mescladas da sessão, se o seu couber no esquema da chave A chamada desse mod resolve para o seu value, ou para a key que ele solicitou extraída do seu value, então retorne um objeto inteiro e não um array
{ deny: reason } A sessão mantém as configurações como estavam mescladas antes de qualquer hook responder, e o log de depuração registra o seu reason A chamada desse mod é rejeitada com <calling mod>: $.settings.read: <reason>

Na leitura do próprio Claude Code, monte o seu { value } a partir do value de next(e), já que uma chave que você omitir fica indefinida. Onde o Claude Code lê uma chave como env de cada fonte separadamente, alterá-la no seu value não tem efeito.

O que next(e) fornece a um hook settings.read

next(e) resolve para { value } ou para { deny }:

  • { value }: value são as configurações mescladas, ou as configurações de uma fonte quando e.source está definido. Se um hook que é executado depois do seu retornou um { value } próprio, você recebe o value desse hook.
  • { deny }: um hook executado depois do seu recusou a leitura. Retorne esse { deny } sem alterações.

Alterar uma configuração a partir de um hook settings.read

Primeiro verifique quais mods recebem este evento, e a partir de qual versão.

Para alterar uma chave, espalhe o value de next(e) e defina essa chave, como este hook faz com spinnerTipsEnabled. O hook não verifica next.origin, então ele altera a chave tanto na leitura do próprio Claude Code quanto na leitura mesclada de um mod:

on('settings.read', async ($, e, next) => {
  const read = await next(e)
  // Pass through a read of one source, so that only the merged settings change.
  // Pass through a { deny } from a later hook too: it has no value.
  if (e.source || !read.value) return read
  // Keep every other key and change this one
  return { value: { ...read.value, spinnerTipsEnabled: false } }
})

Na leitura do próprio Claude Code, se o seu hook retornar um valor fora do esquema da chave, como uma string, apenas essa chave mantém o valor mesclado antes de qualquer hook responder. O log de depuração então informa the answer for <key> is no value the key can hold. Se o seu valor couber e a chave ainda assim não mudar, consulte por que uma chave não tem efeito.

Tratar os eventos de hooks de configuração

Hooks de configuração são os hooks de comando, HTTP, prompt e agente que você configura em arquivos de configuração. Cada evento de hook de configuração, como Stop, SessionEnd ou PostToolUse, também é um evento nomeado classic. seguido do nome do evento de hook de configuração, como classic.Stop. e é o JSON que um hook de configuração recebe no stdin, incluindo transcript_path. Onde a guarda integrada é carregada, um hook nesses eventos em um mod que você instala não é executado.

Este hook usa Stop, que é disparado quando o Claude termina de responder, para registrar em log onde a transcrição da sessão está salva:

on('classic.Stop', async ($, e, next) => {
  // e has the same fields a Stop hook in a settings file reads from stdin
  $.ui.log('Transcript saved at ' + e.transcript_path)
  // Pass the event on, so Stop hooks in your settings files still run
  return next(e)
})

Cada vez que o Claude termina de responder, uma linha esmaecida na transcrição informa o caminho do arquivo de transcrição. O hook retorna next(e), então ele observa o evento e não altera nada em como o turno termina.

Execute ao lado de outros mods

Vários mods podem tratar 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:

  1. A guarda integrada sec-default@builtin, um mod integrado em Claude Code que /plugin lista como cc-plugin-sec-default, onde ele carrega, mods que sua organização lista em prependPlugins e depois qualquer outro mod que conta como de sua organização e não está em appendPlugins
  2. Mods que você instala
  3. Mods que sua organização lista em appendPlugins
  4. 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ção 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 PreToolUse de configurações gerenciadas: são executados antes do hook tool.call do primeiro mod. Quando um hook gerenciado bloqueia a chamada, nenhum hook tool.call de mod é executado para essa chamada.
  • Hooks PreToolUse de cada outro arquivo de configurações e de hooks/hooks.json de plugins: são executados após o último mod chamar next, como parte do comportamento próprio de Claude Code. Um mod que responde tool.call sem chamar next os impede de serem executados, e um mod que chama next vê sua decisão no resultado que retorna.

tool.check dispara após os hooks PreToolUse terem sido executados. Para saber quais regras de permissão e decisões de hooks PreToolUse o allow de um hook tool.check não pode substituir, consulte Estenda permissões com hooks.

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 next ser 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 cuja árvore não valida conta como um que retornou um resultado da forma errada.

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, e o manipulador testa next.called para saber se guard já tinha chamado next quando falhou:

// on retorna um registro e .catch anexa um manipulador a esse hook
on('tool.call', { tool: 'Bash' }, guard).catch(async ($, e, next) => {
  // guard já tinha chamado next, então retorne o que voltou
  if (next.called) return next(e)
  // next.error.kind diz por que o manipulador foi chamado, como 'throw' ou 'timeout'
  return { deny: 'The command guard failed, so this command was not run: ' + next.error.kind }
})

Quando guard lança ou expira em uma chamada Bash, Claude Code chama o manipulador com o mesmo evento:

  • guard falhou antes de chamar next: o comando não é executado e Claude lê o texto de deny com o tipo no final
  • guard falhou depois de chamar next: o next(e) do manipulador é resolvido para o resultado que a chamada de guard produziu, sem executar o comando novamente, e Claude lê esse resultado

O manipulador tem seu próprio limite de tempo, mais curto. Se o próprio manipulador lançar ou expirar, Claude Code pula o hook como se ele não tivesse manipulador. Quando guard não tinha chamado next, o comando então prossegue como prosseguiria sem o mod.

O mesmo formato de manipulador serve para uma guarda em prompt.submit ou config.set. Quando next.called for false, retorne a recusa que a referência de eventos lista para esse evento: { drop: 'the reason' } para prompt.submit, { deny: 'the reason' } para config.set.

Em tool.check e plugin.register, uma recusa retornada após next ser resolvido ainda vale, portanto retorne-a sem testar next.called:

Em tool.call, um deny retornado após next ser resolvido retém o resultado, impedindo que chegue a Claude.

Próximos passos