Skip to content

Hooks

Hooks são um mecanismo de disparo automático: você diz ao Jarvis Code CLI, com antecedência, "sempre que X acontecer, execute este script". O script roda na sua máquina local e você pode colocar qualquer lógica dentro dele. Casos de uso típicos:

  • Interceptação de segurança: antes de o agente executar um comando de shell, verificar se ele contém operações perigosas (como rm -rf) e bloquear a execução em caso positivo
  • Notificações no desktop: quando uma tarefa em segundo plano termina, exibir uma notificação do sistema para trazer você de volta a revisar os resultados
  • Verificações automáticas: cada vez que o usuário envia uma mensagem, acrescentar automaticamente alguma informação de contexto (como o branch Git atual)

Como os hooks funcionam

Configurar uma regra de hook exige especificar três coisas: em qual evento disparar, quais alvos corresponder e qual script executar.

Quando disparado, o CLI empacota os detalhes do evento (motivo do disparo, nome da ferramenta, conteúdo do comando etc.) em JSON e os passa ao seu script pela entrada padrão (stdin). O script lê essa informação e decide como responder.

A resposta do script é determinada por duas coisas:

  • Código de saída: 0 significa permitir, 2 significa bloquear, outros valores diferentes de zero permitem por padrão
  • Saída padrão (stdout): pode incluir texto explicativo

Mesmo que o script dê erro ou estoure o tempo limite, o CLI não interrompe seu trabalho por isso — esse desenho de "permitir em caso de falha" se chama fail-open, e evita que erros de hook virem bloqueios.

Nota

Justamente por causa do fail-open, hooks são adequados para alertas e interceptação leve, mas não devem ser usados como única barreira de segurança. Para operações realmente de alto risco, conte com as aprovações de permissão e a confirmação manual.

Início rápido: um hook mínimo

O hook a seguir exibe uma notificação cada vez que uma tarefa em segundo plano termina (no macOS é preciso ter o terminal-notifier instalado):

toml
# Escrito em ~/.jarvis-code/config.toml
[[hooks]]
event = "Notification"           # Disparo: quando o status de uma tarefa em segundo plano muda
matcher = "task\\.completed"     # Só interessam as notificações de conclusão
command = "terminal-notifier -title 'Jarvis Code' -message 'Task done'"

Salve a configuração, inicie uma nova sessão e uma notificação aparecerá na próxima vez que uma tarefa em segundo plano terminar.

Configuração

Todas as regras de hook são escritas no array [[hooks]] em ~/.jarvis-code/config.toml, onde cada entrada é uma regra:

CampoTipoObrigatórioDescrição
eventstringSimNome do evento de disparo; precisa ser uma das entradas da tabela "Referência de eventos" abaixo
matcherstringNãoUma expressão regular para filtrar os alvos do evento; se omitida, corresponde a todos
commandstringSimO comando de shell a executar no disparo
timeoutintegerNãoTempo limite em segundos, de 1 a 600; o padrão é 30 segundos

O [[hooks]] só aceita esses quatro campos; campos extras fazem o arquivo de configuração falhar ao carregar.

Quando várias regras correspondem ao mesmo evento, todos os hooks correspondentes rodam em paralelo; regras com o mesmo valor de command rodam apenas uma vez.

O diretório de trabalho dos comandos de hook é o diretório de projeto da sessão atual. Em plataformas não Windows, processos de hook ficam em um grupo de processos separado; no tempo limite, um sinal é enviado primeiro para dar chance de limpeza e então o processo é encerrado à força.

Formato dos dados do evento

Cada vez que um hook dispara, o CLI passa ao script, via stdin, a seguinte informação base:

json
{
  "hook_event_name": "PreToolUse",
  "session_id": "session_abc",
  "session_title": "Fix the login page",
  "client_type": "kimi_code_cli",
  "cwd": "/path/to/project"
}

Eventos específicos também incluem campos adicionais (como nome da ferramenta e conteúdo do comando); veja a referência de eventos abaixo. Todos os nomes de campo usam snake_case.

Nota

O client_type carrega o identificador de plataforma do host que o CLI envia para o serviço upstream, que continua sendo kimi_code_cli. Ele é um identificador de protocolo, não um nome de produto; faça a correspondência literal.

Valores de retorno

Depois que o script sai, o CLI determina a intenção do hook pelo código de saída:

Código de saídaSignificadoComportamento do CLI
0Saída normal, permitirContinua a execução; o conteúdo de stdout (se houver) pode ser acrescentado ao contexto
2Bloqueio intencionalInterrompe a operação atual; o conteúdo de stderr (impresso via console.error) é usado como motivo do bloqueio
Outro diferente de zeroErro no scriptPermite por padrão (fail-open)
Tempo limite ou falhaExceção no scriptPermite por padrão (fail-open)

Você também pode retornar um objeto JSON por stdout para bloquear:

json
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Use rg em vez de grep"
  }
}

Quais eventos suportam bloqueio?

Apenas os eventos bloqueáveis (PreToolUse, Stop, UserPromptSubmit) têm valores de retorno que afetam o fluxo principal. Todos os demais são eventos de observação — disparam e seguem em frente; o fluxo principal não é afetado, independentemente do que o script retorne.

Referência de eventos

EventoO matcher corresponde aSuporta bloqueio?Descrição
UserPromptSubmitO texto enviado pelo usuárioDisparado quando o usuário envia uma mensagem; o texto retornado é acrescentado ao contexto; se bloqueado, o modelo não é chamado neste turno
UserPromptQueuedO texto do prompt enfileiradoDisparado quando uma mensagem é enfileirada enquanto um turno ainda roda; o payload inclui prompt_id, prompt e queue_length (apenas observação)
PreToolUseNome da ferramentaDisparado antes de uma chamada de ferramenta (antes das verificações de permissão); a ferramenta não executa se bloqueada
StopString vaziaDisparado quando o modelo está prestes a encerrar o turno atual; se bloqueado, uma mensagem pode ser acrescentada para o modelo continuar
TurnStartedTipo de origem do turno (por exemplo user, task, system_trigger)Disparado quando um novo turno começa; o payload inclui turn_id, origin_kind, origin_name e prompt (apenas observação)
PostToolUseNome da ferramentaDisparado depois de uma ferramenta executar com sucesso (apenas observação)
PostToolUseFailureNome da ferramentaDisparado depois de uma ferramenta falhar ou ser bloqueada (apenas observação)
PermissionRequestNome da ferramentaDisparado imediatamente antes de aguardar a aprovação do usuário (apenas observação)
PermissionResultNome da ferramentaDisparado depois de a aprovação ser concluída (apenas observação)
SessionStartstartup ou resumeDisparado depois de uma nova sessão iniciar ou de uma sessão anterior ser retomada; o payload inclui source, model e profile
SessionEndexit ou archiveDisparado depois de uma sessão fechar; archive significa que a sessão foi arquivada em vez de encerrada
SessionHeartbeatString vaziaDisparado a cada 60 segundos enquanto a sessão está viva; o temporizador só roda quando este evento está configurado. O payload inclui uptime_ms (apenas observação)
SubagentStartNome do subagenteDisparado antes de um subagente começar a rodar
SubagentStopNome do subagenteDisparado depois de um subagente concluir com sucesso (apenas observação)
TaskStartedTipo de tarefa (agent, process ou question)Disparado quando uma tarefa em segundo plano começa; o payload inclui task_id, description e detached (apenas observação)
StopFailureTipo de erroDisparado depois de o turno atual falhar por um erro (apenas observação)
InterruptString vaziaDisparado quando o usuário interrompe o turno atual (por exemplo, pressionando Esc); não dispara em tempos limite ou outros abortos programáticos. O Stop não dispara em interrupções, então este evento dispara no lugar. O payload inclui um campo reason (apenas observação)
PreCompactmanual ou autoDisparado antes de a compactação de contexto começar; valores de retorno são completamente ignorados
PostCompactmanual ou autoDisparado depois de a compactação de contexto terminar (apenas observação)
NotificationTipo de notificação (por exemplo task.completed)Disparado quando o status de uma tarefa em segundo plano muda (apenas observação)

Exemplo: bloquear comandos de shell perigosos

O hook a seguir verifica o conteúdo do comando antes de o agente chamar a ferramenta Bash e o bloqueia se detectar rm -rf:

toml
[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.jarvis-code/hooks/block-dangerous-bash.mjs"
timeout = 5
js
// block-dangerous-bash.mjs
// Lê do stdin os dados de evento passados pelo CLI
let input = '';
process.stdin.on('data', (chunk) => { input += chunk; });
process.stdin.on('end', () => {
  const payload = JSON.parse(input);         // Analisa os dados do evento
  const command = payload.tool_input?.command ?? '';

  if (command.includes('rm -rf')) {
    // Explica o motivo do bloqueio via stderr; código de saída 2 significa bloquear
    console.error('Comando perigoso detectado, bloqueado');
    process.exit(2);
  }
  // Saída normal (código 0) significa permitir
});

Depois de bloquear, o Jarvis Code CLI escreve o motivo do bloqueio de volta no contexto, e o modelo pode usar isso para escolher uma alternativa mais segura.

Nota

Este exemplo apenas demonstra o mecanismo de bloqueio — não é um analisador de segurança de nível de produção. Cenários reais são melhor atendidos por listas de permissão, ou por um analisador de shell dedicado que trate aspas, expansão de variáveis e sequências com vários comandos.

Próximos passos

  • Configuração — referência completa de campos de [[hooks]] no config.toml
  • Agentes e subagentes — use o evento SubagentStop para disparar notificações após um subagente concluir