Hooks permitem que você intercepte a execução do agente em pontos-chave para adicionar validação, logging, controles de segurança ou lógica personalizada. Com hooks, você pode:
Um hook tem duas partes:
PreToolUse) e quais ferramentas corresponderO exemplo a seguir bloqueia o agente de modificar arquivos .env. Primeiro, defina um callback que verifica o caminho do arquivo, depois passe-o para query() para executar antes de qualquer chamada de ferramenta Write ou Edit:
Este é um hook PreToolUse. Ele é executado antes da ferramenta ser executada e pode bloquear ou permitir operações com base em sua lógica. O restante deste guia cobre todos os hooks disponíveis, suas opções de configuração e padrões para casos de uso comuns.
O SDK fornece hooks para diferentes estágios de execução do agente. Alguns hooks estão disponíveis em ambos os SDKs, enquanto outros são apenas para TypeScript porque o SDK Python não os suporta.
| Hook Event | Python SDK | TypeScript SDK | O que dispara | Caso de uso de exemplo |
|---|---|---|---|---|
PreToolUse | Sim | Sim | Solicitação de chamada de ferramenta (pode bloquear ou modificar) | Bloquear comandos shell perigosos |
PostToolUse | Sim | Sim | Resultado da execução da ferramenta | Registrar todas as alterações de arquivo na trilha de auditoria |
PostToolUseFailure | Não | Sim | Falha na execução da ferramenta | Lidar ou registrar erros de ferramenta |
UserPromptSubmit | Sim | Sim | Envio de prompt do usuário |
Hooks são flexíveis o suficiente para lidar com muitos cenários diferentes. Aqui estão alguns dos padrões mais comuns organizados por categoria.
Para configurar um hook para seu agente, passe o hook no parâmetro options.hooks ao chamar query():
async for message in query(
prompt="Your prompt",
options=ClaudeAgentOptions(
hooks={
'PreToolUse': [HookMatcher(matcher='Bash', hooks=[my_callback])]
}
)
):
print(message)A opção hooks é um dicionário (Python) ou objeto (TypeScript) onde:
'PreToolUse', 'PostToolUse', 'Stop')Suas funções de callback de hook recebem dados de entrada sobre o evento e retornam uma resposta para que o agente saiba permitir, bloquear ou modificar a operação.
Use matchers para filtrar quais ferramentas disparam seus callbacks:
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
matcher | string | undefined | Padrão regex para corresponder nomes de ferramentas. As ferramentas integradas incluem Bash, Read, Write, Edit, Glob, Grep, WebFetch, Task e outras. As ferramentas MCP usam o padrão mcp__<server>__<action>. |
hooks | HookCallback[] |
Use o padrão matcher para direcionar ferramentas específicas sempre que possível. Um matcher com 'Bash' é executado apenas para comandos Bash, enquanto omitir o padrão executa seus callbacks para cada chamada de ferramenta. Observe que matchers apenas filtram por nome da ferramenta, não por caminhos de arquivo ou outros argumentos—para filtrar por caminho de arquivo, verifique tool_input.file_path dentro de seu callback.
Matchers se aplicam apenas a hooks baseados em ferramentas (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest). Para hooks de ciclo de vida como Stop, SessionStart e Notification, matchers são ignorados e o hook dispara para todos os eventos desse tipo.
Descobrindo nomes de ferramentas: Verifique o array tools na mensagem do sistema inicial quando sua sessão começar, ou adicione um hook sem um matcher para registrar todas as chamadas de ferramenta.
Nomenclatura de ferramentas MCP: As ferramentas MCP sempre começam com mcp__ seguidas pelo nome do servidor e ação: mcp__<server>__<action>. Por exemplo, se você configurar um servidor chamado playwright, suas ferramentas serão nomeadas mcp__playwright__browser_screenshot, mcp__playwright__browser_click, etc. O nome do servidor vem da chave que você usa na configuração mcpServers.
Este exemplo usa um matcher para executar um hook apenas para ferramentas que modificam arquivos quando o evento PreToolUse dispara:
options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
HookMatcher(matcher='Write|Edit', hooks=[validate_file_path])
]
}
)Cada callback de hook recebe três argumentos:
dict / HookInput): Detalhes do evento. Veja dados de entrada para camposstr | None / string | null): Correlacionar eventos PreToolUse e PostToolUseHookContext): Em TypeScript, contém uma propriedade signal (AbortSignal) para cancelamento. Passe isso para operações assíncronas como fetch() para que elas se cancelem automaticamente se o hook expirar. Em Python, este argumento é reservado para uso futuro.O primeiro argumento para seu callback de hook contém informações sobre o evento. Os nomes dos campos são idênticos entre SDKs (ambos usam snake_case).
Campos comuns presentes em todos os tipos de hook:
| Campo | Tipo | Descrição |
|---|---|---|
hook_event_name | string | O tipo de hook (PreToolUse, PostToolUse, etc.) |
session_id | string | Identificador de sessão atual |
transcript_path | string | Caminho para a transcrição da conversa |
cwd | string | Diretório de trabalho atual |
Campos específicos do hook variam por tipo de hook. Itens marcados TS estão disponíveis apenas no SDK TypeScript:
| Campo | Tipo | Descrição | Hooks |
|---|---|---|---|
tool_name | string | Nome da ferramenta sendo chamada | PreToolUse, PostToolUse, PostToolUseFailureTS, PermissionRequestTS |
tool_input | object | Argumentos passados para a ferramenta | PreToolUse, PostToolUse, PostToolUseFailureTS, PermissionRequestTS |
tool_response | any | Resultado retornado da execução da ferramenta | PostToolUse |
error | string |
O código abaixo define um callback de hook que usa tool_name e tool_input para registrar detalhes sobre cada chamada de ferramenta:
Sua função de callback retorna um objeto que informa ao SDK como proceder. Retorne um objeto vazio {} para permitir a operação sem alterações. Para bloquear, modificar ou adicionar contexto à operação, retorne um objeto com um campo hookSpecificOutput contendo sua decisão.
Campos de nível superior (fora de hookSpecificOutput):
| Campo | Tipo | Descrição |
|---|---|---|
continue | boolean | Se o agente deve continuar após este hook (padrão: true) |
stopReason | string | Mensagem mostrada quando continue é false |
suppressOutput | boolean | Ocultar stdout da transcrição (padrão: false) |
systemMessage | string |
Campos dentro de hookSpecificOutput:
| Campo | Tipo | Hooks | Descrição |
|---|---|---|---|
hookEventName | string | Todos | Obrigatório. Use input.hook_event_name para corresponder ao evento atual |
permissionDecision | 'allow' | 'deny' | 'ask' | PreToolUse | Controla se a ferramenta é executada |
permissionDecisionReason | string | PreToolUse | Explicação mostrada a Claude para a decisão |
updatedInput |
Este exemplo bloqueia operações de gravação no diretório /etc enquanto injeta uma mensagem do sistema para lembrar Claude sobre práticas seguras de arquivo:
Quando múltiplos hooks ou regras de permissão se aplicam, o SDK os avalia nesta ordem:
Se qualquer hook retornar deny, a operação é bloqueada—outros hooks retornando allow não a substituirão.
Retorne uma decisão de negação para impedir a execução da ferramenta:
Retorne entrada atualizada para alterar o que a ferramenta recebe:
Ao usar updatedInput, você também deve incluir permissionDecision. Sempre retorne um novo objeto em vez de mutar o tool_input original.
Injetar contexto na conversa:
async def add_security_reminder(input_data, tool_use_id, context):
return {
'systemMessage': 'Remember to follow security best practices.'
}Contornar prompts de permissão para ferramentas confiáveis. Isso é útil quando você quer que certas operações sejam executadas sem confirmação do usuário:
O campo permissionDecision aceita três valores: 'allow' (aprovar automaticamente), 'deny' (bloquear) ou 'ask' (solicitar confirmação).
Esses padrões ajudam você a construir sistemas de hook mais sofisticados para casos de uso complexos.
Hooks são executados na ordem em que aparecem no array. Mantenha cada hook focado em uma única responsabilidade e encadeie múltiplos hooks para lógica complexa. Este exemplo executa todos os quatro hooks para cada chamada de ferramenta (nenhum matcher especificado):
Use padrões regex para corresponder múltiplas ferramentas:
Matchers apenas correspondem nomes de ferramentas, não caminhos de arquivo ou outros argumentos. Para filtrar por caminho de arquivo, verifique tool_input.file_path dentro de seu callback de hook.
Use hooks SubagentStop para monitorar a conclusão de subagente. O tool_use_id ajuda a correlacionar chamadas de agente pai com seus subagentes:
Hooks podem realizar operações assíncronas como requisições HTTP. Lidar com erros graciosamente capturando exceções em vez de lançá-las. Em TypeScript, passe o signal para fetch() para que a requisição seja cancelada se o hook expirar:
Use hooks Notification para receber atualizações de status do agente e encaminhá-las para serviços externos como Slack ou painéis de monitoramento:
import { query, HookCallback, NotificationHookInput } from "@anthropic-ai/claude-agent-sdk";
const notificationHandler: HookCallback = async (input, toolUseID, { signal }) => {
const notification = input as NotificationHookInput;
await fetch('https://hooks.slack.com/services/YOUR/WEBHOOK/URL', {
method: 'POST',
body: JSON.stringify({
text: `Agent status: ${notification.message}`
}),
signal
});
return {};
};
for await (const message of query({
prompt: "Analyze this codebase",
options: {
hooks: {
Notification: [{ hooks: [notificationHandler] }]
}
}
})) {
console.log(message);
}Esta seção cobre problemas comuns e como resolvê-los.
PreToolUse, não preToolUse)options.hooksSubagentStop, Stop, SessionStart, SessionEnd e Notification, matchers são ignorados. Esses hooks disparam para todos os eventos desse tipo.max_turns porque a sessão termina antes dos hooks poderem ser executadosMatchers apenas correspondem nomes de ferramentas, não caminhos de arquivo ou outros argumentos. Para filtrar por caminho de arquivo, verifique tool_input.file_path dentro de seu hook:
const myHook: HookCallback = async (input, toolUseID, { signal }) => {
const preInput = input as PreToolUseHookInput;
const filePath = preInput.tool_input?.file_path as string;
if (!filePath?.endsWith('.md')) return {}; // Skip non-markdown files
// Process markdown files...
};timeout na configuração HookMatcherAbortSignal do terceiro argumento de callback para lidar com cancelamento graciosamente em TypeScriptPreToolUse para retornos permissionDecision: 'deny'permissionDecisionReason eles estão retornandoCertifique-se de que updatedInput está dentro de hookSpecificOutput, não no nível superior:
return {
hookSpecificOutput: {
hookEventName: input.hook_event_name,
permissionDecision: 'allow',
updatedInput: { command: 'new command' }
}
};Você também deve retornar permissionDecision: 'allow' para que a modificação de entrada tenha efeito
Hooks SessionStart, SessionEnd e Notification estão disponíveis apenas no SDK TypeScript. O SDK Python não suporta esses eventos devido a limitações de configuração.
Ao gerar múltiplos subagentes, cada um pode solicitar permissões separadamente. Subagentes não herdam automaticamente permissões de agente pai. Para evitar prompts repetidos, use hooks PreToolUse para aprovar automaticamente ferramentas específicas, ou configure regras de permissão que se apliquem a sessões de subagente.
Um hook UserPromptSubmit que gera subagentes pode criar loops infinitos se esses subagentes disparem o mesmo hook. Para evitar isso:
parent_tool_use_id para detectar se você já está em um contexto de subagenteO campo systemMessage adiciona contexto à conversa que o modelo vê, mas pode não aparecer em todos os modos de saída do SDK. Se você precisar exibir decisões de hook para sua aplicação, registre-as separadamente ou use um canal de saída dedicado.
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher
# Define a hook callback that receives tool call details
async def protect_env_files(input_data, tool_use_id, context):
# Extract the file path from the tool's input arguments
file_path = input_data['tool_input'].get('file_path', '')
file_name = file_path.split('/')[-1]
# Block the operation if targeting a .env file
if file_name == '.env':
return {
'hookSpecificOutput': {
'hookEventName': input_data['hook_event_name'],
'permissionDecision': 'deny',
'permissionDecisionReason': 'Cannot modify .env files'
}
}
# Return empty object to allow the operation
return {}
async def main():
async for message in query(
prompt="Update the database configuration",
options=ClaudeAgentOptions(
hooks={
# Register the hook for PreToolUse events
# The matcher filters to only Write and Edit tool calls
'PreToolUse': [HookMatcher(matcher='Write|Edit', hooks=[protect_env_files])]
}
)
):
print(message)
asyncio.run(main())| Injetar contexto adicional em prompts |
Stop | Sim | Sim | Parada de execução do agente | Salvar estado da sessão antes de sair |
SubagentStart | Não | Sim | Inicialização de subagentte | Rastrear geração de tarefas paralelas |
SubagentStop | Sim | Sim | Conclusão de subagente | Agregar resultados de tarefas paralelas |
PreCompact | Sim | Sim | Solicitação de compactação de conversa | Arquivar transcrição completa antes de resumir |
PermissionRequest | Não | Sim | Diálogo de permissão seria exibido | Manipulação de permissão personalizada |
SessionStart | Não | Sim | Inicialização de sessão | Inicializar logging e telemetria |
SessionEnd | Não | Sim | Encerramento de sessão | Limpar recursos temporários |
Notification | Não | Sim | Mensagens de status do agente | Enviar atualizações de status do agente para Slack ou PagerDuty |
| - |
| Obrigatório. Array de funções de callback para executar quando o padrão corresponder |
timeout | number | 60 | Timeout em segundos; aumente para hooks que fazem chamadas de API externas |
| Mensagem de erro da falha na execução da ferramenta |
| PostToolUseFailureTS |
is_interrupt | boolean | Se a falha foi causada por uma interrupção | PostToolUseFailureTS |
prompt | string | O texto do prompt do usuário | UserPromptSubmit |
stop_hook_active | boolean | Se um hook de parada está sendo processado | Stop, SubagentStop |
agent_id | string | Identificador único para o subagente | SubagentStartTS, SubagentStopTS |
agent_type | string | Tipo/função do subagente | SubagentStartTS |
agent_transcript_path | string | Caminho para a transcrição da conversa do subagente | SubagentStopTS |
trigger | string | O que disparou a compactação: manual ou auto | PreCompact |
custom_instructions | string | Instruções personalizadas fornecidas para compactação | PreCompact |
permission_suggestions | array | Atualizações de permissão sugeridas para a ferramenta | PermissionRequestTS |
source | string | Como a sessão começou: startup, resume, clear ou compact | SessionStartTS |
reason | string | Por que a sessão terminou: clear, logout, prompt_input_exit, bypass_permissions_disabled ou other | SessionEndTS |
message | string | Mensagem de status do agente | NotificationTS |
notification_type | string | Tipo de notificação: permission_prompt, idle_prompt, auth_success ou elicitation_dialog | NotificationTS |
title | string | Título opcional definido pelo agente | NotificationTS |
async def log_tool_calls(input_data, tool_use_id, context):
if input_data['hook_event_name'] == 'PreToolUse':
print(f"Tool: {input_data['tool_name']}")
print(f"Input: {input_data['tool_input']}")
return {}| Mensagem injetada na conversa para Claude ver |
object |
| PreToolUse |
Entrada de ferramenta modificada (requer permissionDecision: 'allow') |
additionalContext | string | PostToolUse, UserPromptSubmit, SessionStartTS, SubagentStartTS | Contexto adicionado à conversa |
async def block_etc_writes(input_data, tool_use_id, context):
file_path = input_data['tool_input'].get('file_path', '')
if file_path.startswith('/etc'):
return {
# Top-level field: inject guidance into the conversation
'systemMessage': 'Remember: system directories like /etc are protected.',
# hookSpecificOutput: block the operation
'hookSpecificOutput': {
'hookEventName': input_data['hook_event_name'],
'permissionDecision': 'deny',
'permissionDecisionReason': 'Writing to /etc is not allowed'
}
}
return {}async def block_dangerous_commands(input_data, tool_use_id, context):
if input_data['hook_event_name'] != 'PreToolUse':
return {}
command = input_data['tool_input'].get('command', '')
if 'rm -rf /' in command:
return {
'hookSpecificOutput': {
'hookEventName': input_data['hook_event_name'],
'permissionDecision': 'deny',
'permissionDecisionReason': 'Dangerous command blocked: rm -rf /'
}
}
return {}async def redirect_to_sandbox(input_data, tool_use_id, context):
if input_data['hook_event_name'] != 'PreToolUse':
return {}
if input_data['tool_name'] == 'Write':
original_path = input_data['tool_input'].get('file_path', '')
return {
'hookSpecificOutput': {
'hookEventName': input_data['hook_event_name'],
'permissionDecision': 'allow',
'updatedInput': {
**input_data['tool_input'],
'file_path': f'/sandbox{original_path}'
}
}
}
return {}async def auto_approve_read_only(input_data, tool_use_id, context):
if input_data['hook_event_name'] != 'PreToolUse':
return {}
read_only_tools = ['Read', 'Glob', 'Grep', 'LS']
if input_data['tool_name'] in read_only_tools:
return {
'hookSpecificOutput': {
'hookEventName': input_data['hook_event_name'],
'permissionDecision': 'allow',
'permissionDecisionReason': 'Read-only tool auto-approved'
}
}
return {}options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
HookMatcher(hooks=[rate_limiter]), # First: check rate limits
HookMatcher(hooks=[authorization_check]), # Second: verify permissions
HookMatcher(hooks=[input_sanitizer]), # Third: sanitize inputs
HookMatcher(hooks=[audit_logger]) # Last: log the action
]
}
)options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
# Match file modification tools
HookMatcher(matcher='Write|Edit|Delete', hooks=[file_security_hook]),
# Match all MCP tools
HookMatcher(matcher='^mcp__', hooks=[mcp_audit_hook]),
# Match everything (no matcher)
HookMatcher(hooks=[global_logger])
]
}
)async def subagent_tracker(input_data, tool_use_id, context):
if input_data['hook_event_name'] == 'SubagentStop':
print(f"[SUBAGENT] Completed")
print(f" Tool use ID: {tool_use_id}")
print(f" Stop hook active: {input_data.get('stop_hook_active')}")
return {}
options = ClaudeAgentOptions(
hooks={
'SubagentStop': [HookMatcher(hooks=[subagent_tracker])]
}
)import aiohttp
from datetime import datetime
async def webhook_notifier(input_data, tool_use_id, context):
if input_data['hook_event_name'] != 'PostToolUse':
return {}
try:
async with aiohttp.ClientSession() as session:
await session.post(
'https://api.example.com/webhook',
json={
'tool': input_data['tool_name'],
'timestamp': datetime.now().isoformat()
}
)
except Exception as e:
print(f'Webhook request failed: {e}')
return {}Inclua hookEventName em hookSpecificOutput para identificar qual tipo de hook a saída é