HookEvent lists the events you can attach a hook to with HookMatcher().
When several hooks match an event, they run in the order they were added.
The first callback that returns a non-NULL value decides the outcome, and
the remaining hooks for that event don't run. If a PreToolUse callback
errors or times out, the tool call is denied. Errors in other callbacks are
reported and the run continues.
A callback may return a promise of what it would have returned, so a hook
can wait for a person, a model or a file. Deputy waits for the promise, up
to the hook's promise_timeout, before it goes on:
PreToolUse,PermissionRequest: before the tool runs.PostToolUse,PostToolUseFailure: before the next model request. The tool's result has already been recorded when the hook answers.PostModelRequest,SessionStart,UserPromptSubmit,SubagentStart,SubagentStop, andPreCompactandPostCompactduring automatic compaction: before the run goes on.Stop,SessionEnd: before the run finishes.
A promise that is rejected, or has not settled in time, counts as no answer
and is recorded as a hook error; for PreToolUse that denies the call.
Deputy does not wait for Notification or ConfigChange hooks, for a hook
fired by $compact(), or for Stop and SessionEnd when a run ends with
an error or its stream is abandoned. With tools run concurrently, other
tool calls of the same response may start or finish while a hook waits. If
$interrupt() is called while a hook waits, or by the callback itself, the
run stops when the hook answers, and a tool it was asked about does not
run.
Events
Each entry shows the event with its callback's arguments, then when it fires.
PreToolUse(tool_name, tool_input, context): before a tool runs, once the permission policy has allowed it.PostToolUse(tool_name, tool_result, tool_error, context): after a tool call finishes, whether or not it failed.PostToolUseFailure(tool_name, tool_result, tool_error, context): afterPostToolUse, when the tool failed.PostModelRequest(context): after each model request of a run, once its response has arrived: the task's requests, a structured request, and the summary request of an automatic compaction. It needs ellmer's request callbacks, so a stand-in for a Chat that lacks them never fires it.PermissionRequest(tool_name, tool_input, permission_result, context): when the permission policy denies a call.SessionStart(context): at the start of each run.UserPromptSubmit(prompt, context): at the start of each run, afterSessionStart.Stop(reason, context): at each stop of a run. AStophook may returnHookResultStop()to send an answered run on, so a run can stop more than once.SessionEnd(reason, context): once, when the run has ended.SubagentStart(agent_name, task, context): when a subagent starts a delegated task.SubagentStop(agent_name, task, result, context): when a subagent finishes.PreCompact(turns_to_compact, turns_to_keep, context): before older turns are summarised.PostCompact(result, context): after compaction.Notification(message, context): when the agent reports something, such as a denied call.ConfigChange(key, old_value, new_value, context): whenset_permission_mode()changes the mode.
tool_input is the named list of tool arguments. After a successful call
tool_error is NULL; after a failure tool_result is NULL and
tool_error holds the error message. reason is the stop reason, such as
"complete", "request_limit", "cost_limit", "tool_loop",
"hook_requested_stop" or "provider_error". result is the subagent's
result for SubagentStop and the DeputyCompaction for PostCompact. For
ConfigChange, key is "permission_mode".
Five events use the callback's return value:
PreToolUse: returnHookResultPreToolUse()to allow or deny the call, or to stop the run.PostToolUse: returnHookResultPostToolUse()to stop the run or to change what thetool_endevent shows.PermissionRequest: returnPermissionResultAllow()to allow the call anyway, orPermissionResultDeny()to change the reason.PreCompact: returnHookResultPreCompact()to cancel compaction or to supply your own summary.Stop: returnHookResultStop()to send the run on.
A run that is sent on keeps its run ID, its limits and its usage. The
result's response is the text of the last pass, and its turns hold
every pass. Only an answered run is sent on, and a run that extracts
structured output with $chat_structured() never is.
Other events ignore the return value. Returning NULL means "no decision".
Context
context is a named list. Every event includes:
working_dir: the agent's working directory.agent_id,agent_name,session_id: the agent's identifiers.run_id: the current run, when one is active.run_context: the run'srun_contextlist.parent_agent_id,parent_run_id,delegation_id: set in subagent runs.
Some events add fields:
tool_call_id,permission_mode,usage,usage_limits(tool events): the call's ID, the permission mode, the run's AgentUsage so far and its UsageLimits.usage,usage_limits,request_number(PostModelRequest): the run's AgentUsage so far, its UsageLimits and the request's number in the run.tool_annotations,tool_arguments,tool_metadata(PreToolUse, PermissionRequest): the tool's annotations, declared arguments andtool_metadata(), when available.usage,cost,ending(Stop, SessionEnd): the run's AgentUsage, the conversation's cost, as returned byagent$cost(), and the run's RunEnding as it stands.stop_hook_active(Stop):TRUEwhen a hook has already sent this run on.total_turns,compact_count(PreCompact, PostCompact): the number of turns in the conversation and the number being summarised.automatic(PostCompact):TRUEwhen compaction ran automatically during a run rather than throughcompact().child_agent_id,child_run_id,status(SubagentStart, SubagentStop): the subagent's identifiers and status.level,code(Notification): a severity such as"info"or"warning", and a notification code when there is one.permissions,provider,tools_count(SessionStart): the agent's Permissions, a list with the providernameandmodel, and the number of registered tools.
Examples
if (FALSE) { # \dontrun{
# Log each tool call. Returning NULL leaves the decision to other hooks.
agent$add_hook(HookMatcher(
event = "PreToolUse",
callback = function(tool_name, tool_input, context) {
message("Tool: ", tool_name, " in ", context$working_dir)
NULL
}
))
# Stop the run when a tool fails
agent$add_hook(HookMatcher(
event = "PostToolUse",
callback = function(tool_name, tool_result, tool_error, context) {
if (!is.null(tool_error)) {
return(HookResultPostToolUse(continue = FALSE))
}
NULL
}
))
} # }