Uma tarefa falha de madrugada, sem ninguém olhando o terminal: hoje, no Verboo Code, ela fica falha até alguém entrar e mandar rodar de novo. Hook chains resolvem exatamente isso. É uma camada de recuperação orientada a evento que, quando um hook de falha dispara, avalia regras declarativas e executa uma ação, como recuperar a tarefa com um agente novo ou avisar o time. É um recurso real, desligado por padrão. Este artigo mostra o comando exato pra ligar e o arquivo de configuração completo, verificado direto no código-fonte de verbeux-ai/code (src/utils/hookChains.ts, docs/hook-chains.md).
O que dispara uma hook chain?
Hoje, dois eventos. PostToolUseFailure, quando uma tool falha durante a execução, e TaskCompleted com outcome: "failed", quando uma tarefa termina sem sucesso. Cada regra declara a qual evento e a qual resultado ela reage; o resultado aceita um valor único em outcome ou uma lista em outcomes (nunca os dois ao mesmo tempo, o próprio schema rejeita).
Como eu ligo hook chains no Verboo Code?
Duas coisas. Primeiro, a variável de ambiente que liga o recurso inteiro, porque ele vem desligado por padrão mesmo que exista um arquivo de configuração:
export CLAUDE_CODE_ENABLE_HOOK_CHAINS=1
# aceita tambem: true, yes, on
Segundo, o arquivo de regras. O caminho padrão é .verboo/hook-chains.json, na raiz do projeto. Pra usar outro caminho:
export CLAUDE_CODE_HOOK_CHAINS_CONFIG_PATH=/caminho/para/hook-chains.json
Sem a variável de ativação, o Verboo Code nem chega a ler o arquivo: a checagem de enabled acontece antes de qualquer tentativa de carregar a configuração do disco.
Qual é o formato do arquivo de configuração?
Um objeto com quatro campos de controle geral e uma lista de regras. Pode vir direto na raiz do JSON ou envolvido numa chave hookChains, as duas formas são aceitas.
| Campo | Tipo | Padrão |
|---|---|---|
version | número | 1 |
enabled | booleano | true |
maxChainDepth | inteiro, 1 a 10 | 2 |
defaultCooldownMs | inteiro | 30000 |
defaultDedupWindowMs | inteiro | 30000 |
rules | lista | [] |
Um exemplo mínimo que recupera uma tarefa falha rodando um agente novo:
{
"version": 1,
"enabled": true,
"maxChainDepth": 2,
"defaultCooldownMs": 30000,
"defaultDedupWindowMs": 30000,
"rules": [
{
"id": "retry-task-via-fallback",
"trigger": { "event": "TaskCompleted", "outcome": "failed" },
"cooldownMs": 60000,
"actions": [
{
"type": "spawn_fallback_agent",
"id": "spawn-retry-agent",
"description": "Retry failed task with fallback agent",
"promptTemplate": "A task failed. Recover it safely.\nTask=${TASK_SUBJECT}\nError=${ERROR}",
"agentType": "general-purpose",
"model": "sonnet"
}
]
}
]
}
Como eu miro a regra só em erros específicos?
Com o bloco condition, dentro da regra. Ele é opcional, mas sem ele a regra reage a qualquer ocorrência do evento e do outcome declarados.
| Campo | O que faz |
|---|---|
toolNames | lista de nomes de tool que a regra aceita (bate com tool_name/toolName do evento) |
taskStatuses | lista de status de tarefa aceitos |
errorIncludes | substring, sem diferenciar maiúscula de minúscula, contra a mensagem de erro |
eventFieldEquals | igualdade exata por caminho de campo, por exemplo "meta.source": "scheduler" |
"condition": {
"toolNames": ["Edit", "Write", "Bash"],
"errorIncludes": ["timeout", "permission denied"]
}
Dá pra só avisar o time, sem rodar agente nenhum?
Dá, com a ação notify_team no lugar de spawn_fallback_agent. Ela lê o time configurado no projeto e escreve a mensagem sem precisar disparar um agente novo:
{
"type": "notify_team",
"id": "notify-ops",
"recipients": ["*"],
"summary": "Hook chain ${RULE_ID} fired",
"messageTemplate": "Event=${EVENT_NAME} outcome=${OUTCOME}\nTask=${TASK_ID}\nError=${ERROR}"
}
Se não houver arquivo de time configurado, a ação não quebra a cadeia: ela é pulada, com o motivo registrado. As duas ações, e uma terceira (warm_remote_capacity, que avisa o runtime pra preparar capacidade de execução remota com antecedência), aceitam os mesmos placeholders de texto:
| Placeholder | Preenchido com |
|---|---|
${EVENT_NAME} | o evento que disparou a regra |
${OUTCOME} | o resultado do evento |
${RULE_ID} | o id da regra que bateu |
${TASK_SUBJECT} | o assunto da tarefa |
${TASK_DESCRIPTION} | a descrição da tarefa |
${TASK_ID} | o identificador da tarefa |
${ERROR} | a mensagem de erro |
${PAYLOAD_JSON} | o payload completo do evento, em JSON |
warm_remote_capacity é seguro deixar configurado mesmo sem usar execução remota: ela é pulada sem erro quando a política do projeto bloqueia sessão remota, ou quando não há nenhuma sessão remota ativa pra preparar.
O que impede um loop de recuperação sem fim?
Três travas, e elas se somam. maxChainDepth corta o disparo quando a profundidade da cadeia atual já alcançou o limite (padrão 2, teto 10): uma ação de hook chain que dispara outro evento não gera uma cadeia infinita. cooldownMs impede que a mesma regra dispare de novo antes do tempo configurado passar (30 segundos por padrão, ajustável por regra). dedupWindowMs suprime a repetição da mesma combinação de evento e ação dentro da janela, então uma rajada do mesmo erro não dispara o mesmo agente várias vezes seguidas. Some a isso o comportamento seguro por padrão: se o sinal atual já foi abortado, a ação é pulada sem tentar rodar em cima de um estado que não existe mais.
verbeux-ai/code, src/utils/hookChains.ts, 11/09/2026.Cada vez que uma regra dispara spawn_fallback_agent, um agente roda do zero pra tentar recuperar a tarefa. Numa madrugada em que o mesmo erro se repete algumas vezes antes do cooldownMs deixar a regra quieta, isso significa vários agentes de recuperação seguidos. Na Verboo Code, cada um deles roda com token ilimitado, então a quinta tentativa de recuperação custa o mesmo que a primeira.



