Hooks reference
Every event, field and exit code. To get started, see Hooks.
Configurationâ
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.abacusai/hooks/guard.sh",
"if": "Bash(git *)",
"timeout": 30,
"statusMessage": "Checking the command"
}
]
}
]
},
"disableAllHooks": false
}
Hooks are read from ~/.abacusai/config.json, from <project>/.abacusai/config.json in a trusted workspace, and from each --settings <file>. Hooks from every file run. Identical handlers matched by one event run once. "disableAllHooks": true in any of these files turns every hook off.
Handler fieldsâ
| Field | Default | What it does |
|---|---|---|
type | "command", the only type that runs | |
command | The command line, run by shell. With args, the program to run directly, with no shell. | |
args | Arguments for command, which then runs without a shell | |
shell | bash | bash or powershell |
timeout | 600 | Seconds before the hook is stopped |
statusMessage | Shown while the hook runs | |
if | A permission rule the tool call must match. See The if field. | |
once | false | Run at most once per session |
async | false | Run in the background. Its output decides nothing. |
onFailure | continue | block treats a failed hook (crash, timeout, invalid JSON) as a block, so the hook fails closed |
Matchersâ
| Matcher | Matches |
|---|---|
absent, "" or * | Everything |
Letters, digits, _, -, spaces, | and , only | An exact name, or a list: Bash, Edit|Write |
| Anything else | A JavaScript regular expression: mcp__github__.*, ^(Read|Grep)$ |
What the matcher is compared with depends on the event:
| Event | Matcher is compared with |
|---|---|
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied | The tool name |
SessionStart | source: startup, resume, clear, compact, fork |
SessionEnd | reason: clear, resume, logout, prompt_input_exit, other |
Notification | notification_type: permission_prompt, agent_needs_input, idle_prompt |
StopFailure | error |
SubagentStart, SubagentStop | The agent type on a local provider. On Abacus.AI models only * matches. |
PreCompact, PostCompact | trigger: manual or auto |
ConfigChange | source: user_settings or project_settings |
PostModelSwitch | The model id switched to |
UserPromptSubmit, Stop, CwdChanged | Nothing. The matcher is ignored. |
The if fieldâ
if is one permission rule, such as Bash(git commit *) or Edit(src/**). The hook runs only for calls the rule matches. Shell rules see every part of a compound command, so Bash(git commit *) also matches cd repo && git commit -m x. if applies to tool events only.
Eventsâ
| Event | When it fires | Can block |
|---|---|---|
PreToolUse | Before a tool runs | Yes |
PermissionRequest | When a permission prompt is about to show | Yes, with JSON |
PermissionDenied | When Auto mode refuses a call | No |
PostToolUse | After a tool succeeds | Feedback only |
PostToolUseFailure | After a tool fails | Feedback only |
UserPromptSubmit | Before a prompt is sent, including queued and steered messages | Yes |
Stop | When the agent finishes a turn. Not after you stop it or after an error. | Yes, the agent keeps working |
StopFailure | When a turn ends with an error | No |
SubagentStart | When a subagent starts | No |
SubagentStop | When a subagent finishes | On a local provider |
SessionStart | When a conversation starts, resumes, is cleared, forked or compacted | No |
SessionEnd | When a conversation is cleared, switched away from, signed out of or closed | No |
Notification | When a prompt is waiting for you, or a minute after a turn ends with no reply | No |
PreCompact | Before /compact | Yes |
PostCompact | After a compaction | No |
CwdChanged | When the session's working directory changes | No |
ConfigChange | When your user or project settings file changes | No |
PostModelSwitch | When the model changes inside a conversation | No |
On Abacus.AI modelsâ
Conversations on Abacus.AI models run the agent on Abacus.AI's servers, and your machine runs the tools it is asked to run. Hooks act on what your machine does, so:
- Tools the server runs itself, such as web search and web fetch, fire no hooks. Shell commands, file tools, search, MCP tools, to-dos, plan approval, questions to you and background tasks are all hooked.
- Context from
SessionStart,UserPromptSubmitandPostModelSwitchhooks is not sent to the model. The conversation shows a warning. Blocking a prompt and setting the title still work. SubagentStartandSubagentStoponly observe. The subagent has already run.
On a local provider every tool is hooked and all of the above works.
Inputâ
Each hook gets one JSON object on stdin.
Common fieldsâ
| Field | Value |
|---|---|
session_id | The conversation id |
transcript_path | Path to a JSONL transcript of the conversation in Claude Code's format |
cwd | The workspace folder |
permission_mode | default (Supervise), acceptEdits, plan, auto, dontAsk or bypassPermissions (Unsupervised) |
hook_event_name | The event, such as PreToolUse |
Per-event fieldsâ
| Event | Fields beyond the common ones |
|---|---|
PreToolUse | tool_name, tool_input, tool_use_id |
PostToolUse | Same, plus tool_response, duration_ms |
PostToolUseFailure | Same as PreToolUse, plus error, is_interrupt |
PermissionRequest | Same as PreToolUse, plus permission_suggestions |
PermissionDenied | Same as PreToolUse, plus reason |
UserPromptSubmit | prompt |
SessionStart | source, model |
SessionEnd | reason |
Notification | message, title, notification_type |
Stop | stop_hook_active, last_assistant_message, background_tasks |
StopFailure | error, error_details, last_assistant_message |
SubagentStart | agent_id, agent_type |
SubagentStop | Stop's fields, plus agent_id, agent_type |
PreCompact | trigger |
PostCompact | trigger, compact_summary |
CwdChanged | old_cwd, new_cwd |
ConfigChange | source, file_path |
PostModelSwitch | from_model, to_model, requested_model, source |
Tool namesâ
Hooks see Claude Code's tool names and input shapes. Matchers, if and permission rules use the same names.
| Tool | Hooks see | tool_input |
|---|---|---|
| Shell command | Bash | command, timeout, run_in_background |
| Read a file | Read | file_path, offset, limit |
| Write a file | Write | file_path, content |
| Edit a file | Edit | file_path, old_string, new_string, replace_all |
| Delete a file or folder | Bash | command: rm -- '<path>', or rm -rf -- '<path>' for a folder |
| Search contents, find files | Grep, Glob | The tool's arguments |
| MCP tool | mcp__<server>__<tool> | The MCP tool's arguments |
| To-do list, plan approval, question to you | TodoWrite, ExitPlanMode, AskUserQuestion | The tool's arguments |
| Background tasks | TaskOutput, TaskStop, ScheduleWakeup | The tool's arguments |
file_path is always absolute. Because a delete reaches hooks as an rm command, a guard on Bash(rm *) covers it too.
Exit codesâ
| Exit code | Result |
|---|---|
0 | Success. If stdout is one JSON object, it is the hook's output. Otherwise stdout is added as context for SessionStart, UserPromptSubmit and PostModelSwitch, and ignored elsewhere. |
2 | Block. stderr is the reason. |
| Anything else | A failure. You see Hook failed and the work carries on, unless the hook sets "onFailure": "block". |
What exit 2 does depends on the event:
| Event | Effect |
|---|---|
PreToolUse | The tool does not run. The agent is told why. |
PostToolUse, PostToolUseFailure | The tool already ran. The reason is added to its result. |
UserPromptSubmit | The prompt is not sent and goes back to the composer |
Stop, SubagentStop | The agent keeps working, with the reason as its next instruction |
PreCompact | /compact is cancelled |
| Other events | Nothing is blocked. stderr is shown as a warning. A PermissionRequest hook answers with JSON, not exit 2. |
JSON outputâ
Exit 0 and print one JSON object for more control than an exit code gives.
Fields for every eventâ
| Field | What it does |
|---|---|
continue | false stops the turn. On Stop and SubagentStop it lets the agent stop. |
stopReason | Shown when continue is false |
systemMessage | A note shown to you, not to the agent |
decision, reason | "decision": "block" with a reason, on the events where exit 2 blocks |
hookSpecificOutput | The event's own fields, below |
Per eventâ
| Event | hookSpecificOutput fields |
|---|---|
PreToolUse | permissionDecision (allow, deny or ask), permissionDecisionReason, updatedInput, additionalContext |
PostToolUse | updatedToolOutput, updatedMCPToolOutput, additionalContext |
PostToolUseFailure | additionalContext |
PermissionRequest | decision: behavior (allow or deny), updatedInput, updatedPermissions, message, interrupt |
PermissionDenied | retry |
UserPromptSubmit, SessionStart | additionalContext, sessionTitle |
Stop, SubagentStop, SubagentStart, PostModelSwitch | additionalContext |
The other events only observe.
What the decisions doâ
| Output | Effect |
|---|---|
deny | Blocks the call, like exit 2 |
allow | Skips the permission prompt. It does not override a deny or ask rule, and does not approve a write to a protected file, the removal of a critical folder such as your home folder, or an MCP tool whose server requires your confirmation. Those still ask. |
ask | Shows a permission prompt with your reason, in every mode. In Don't ask mode the call is declined unless a PermissionRequest hook allows it. |
| Several hooks disagree | Deny beats ask, and ask beats allow |
updatedInput | Replaces the tool's input. Include every field, since a field you leave out is dropped. The new input is checked against your permission rules again. |
updatedToolOutput | Replaces the result the agent reads |
additionalContext | On a tool event, added to the tool's result. On Stop, continues the turn. |
PermissionRequest decision | allow answers the prompt for you and deny refuses the call. interrupt: true also stops the turn. |
retry: true | On PermissionDenied, tells the agent it may try again |
sessionTitle | Renames the conversation |
A Stop hook can keep the agent going eight times in a row. After that the agent stops and you are told.
A PermissionRequest hook that allows a call can also change permissions with updatedPermissions: add, remove or replace rules, add or remove directories, or set the mode. Each entry's destination decides whether the change lasts for the session only (session) or is also saved to <project>/.abacusai/config.json (projectSettings or localSettings) or ~/.abacusai/config.json (userSettings). See Permissions for what a rule can and cannot approve.
Text a hook returns, such as a reason or context, is capped at 10,000 characters.
Runningâ
Hooks matched by one event run in parallel, and the call waits for all of them. Each runs in the workspace folder. A hook gets 600 seconds unless it sets timeout, and stopping the turn stops its hooks too.
| Environment variable | Value |
|---|---|
CLAUDE_PROJECT_DIR | The workspace folder |
ABACUS_PROJECT_DIR | The same |
Hooks otherwise inherit the environment the CLI or extension runs in, minus ABACUS_API_KEY and ROUTELLM_API_KEY.
Print modeâ
In print mode the same hooks run, under the same trust rule.
With --output-format text or json, blocks, failures and hook notes are written to stderr. If a UserPromptSubmit hook blocks the prompt, it is not sent, the json result has subtype: "prompt_blocked", and the process exits with code 2.
With --output-format stream-json, each hook run is a pair of system lines, hook_started and then hook_response:
{"type":"system","subtype":"hook_started","hook_id":"5fâŚ","hook_name":"PreToolUse:Bash","hook_event":"PreToolUse","tool_call_id":"t1","run":{"status":"running"},"session_id":"c1","uuid":"âŚ"}
{"type":"system","subtype":"hook_response","hook_id":"5fâŚ","hook_name":"PreToolUse:Bash","hook_event":"PreToolUse","tool_call_id":"t1","outcome":"error","exit_code":2,"stderr":"rm is not allowed\n","run":{"status":"blocked","duration_ms":41,"entries":[{"kind":"feedback","text":"rm is not allowed"}]},"session_id":"c1","uuid":"âŚ"}
| Field | Value |
|---|---|
outcome | success, error (exit 2 or a crash) or cancelled (timed out or stopped) |
output, stdout, stderr, exit_code | The raw run |
run.status | running, then completed, blocked, failed or stopped |
run.entries | What the hook said: warning, feedback (a block's reason), stop or error |
Unlike Claude Code, which writes hook lines only with --include-hook-events, these lines are always written. See Scripting and CI for the full protocol.
Differences from Claude Codeâ
The format is Claude Code's: the settings shape, matchers, if, stdin fields, exit codes and JSON output. What differs:
| Claude Code | Here |
|---|---|
Hooks in ~/.claude/settings.json, .claude/settings.json and .claude/settings.local.json | Hooks in .abacusai/config.json files. Copy yours with /import. |
command, http, mcp_tool, prompt and agent handlers | command only. Other types are skipped with a warning. |
| Hooks from plugins, and from skill and subagent frontmatter | Not loaded. A hook that uses CLAUDE_PLUGIN_ROOT needs the variable set in its command. |
PreToolUse can return defer | defer is ignored and the normal permission check applies |
allowManagedHooksOnly and other managed hook policy | Not available |
| Every tool call is hooked | Tools the Abacus.AI server runs itself are not. See On Abacus.AI models. |
These Claude Code events are accepted in your config but never fire, and a warning lists any you use: PostToolBatch, UserPromptExpansion, PreModelSwitch, InstructionsLoaded, DirectoryAdded, WorktreeCreate, WorktreeRemove, FileChanged, MessageDisplay, TaskCreated, TaskCompleted, TeammateIdle, Setup, Elicitation and ElicitationResult.
Sources: Claude Code's Hooks reference and hooks guide.
For further help, reach out at support@abacus.ai.