Setup
Create~/.deepagents/hooks.json for hooks that apply to every project, or {project_root}/.deepagents/hooks.json for project-scoped hooks (after you grant workspace trust). Handlers nest in three levels: event name, matcher group, then the handlers that run for that group.
~/.deepagents/hooks.json
- Project hooks from
{project_root}/.deepagents/hooks.json, after workspace trust is granted. - User hooks from
~/.deepagents/hooks.json. - Hooks contributed by enabled plugins.
dcode config path to inspect the separate project and user hook locations and the workspace trust store.
Hook configuration is snapshotted until /reload or a new session. Editing hooks.json during a turn does not change the active snapshot. Enabling or disabling a plugin also changes the snapshot, so run /reload to pick up its hooks.
Trust project hooks
Project hooks come from the repository, so they load only after the workspace is trusted:- Interactive sessions prompt when an untrusted workspace contains
.deepagents/hooks.json. Trusting the workspace persists the decision for that project root under~/.deepagents/.state/hooks_trust.json. - Denying the prompt skips project hooks for that session and continues with user and plugin hooks.
- Canceling the prompt with
EscorCtrl+Daborts startup. - Headless and CI runs never prompt. Pass
--trust-project-hooksto opt in for that run.
Plugin hooks
An enabled plugin contributes the same configuration shape fromhooks/hooks.json, a manifest hooks path, or an inline manifest object. Installing and enabling the plugin is the consent gate: workspace trust governs project hooks only, so it neither grants nor withholds a plugin’s hooks. Review a plugin before enabling it, and check the events it declares in the plugin manager. See Plugins and marketplaces.
Server-owned events are fixed when a session starts, so newly enabled plugin hooks activate at the next startup or /reload.
Plugin handlers can reference their own installation paths through these variables:
Quote these variables in the
command string, because installation paths can contain spaces: "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/format.sh\"". Prefer the optional argv field when you can: Deep Agents Code resolves the variables before launch and skips the shell, so you do not need quoting.
An invalid plugin hook document is skipped on its own and reported as a configuration diagnostic. Other plugins, project hooks, and user hooks keep working.
Handler fields
Each entry in a matcher group’shooks array is a command handler:
string
required
Handler type. Only
command is supported. A command handler runs a subprocess that receives the event JSON on stdin.string
required
Shell command to run. Always required. Pipes, redirects, globs, and environment-variable expansion are supported. The event payload is written to stdin as JSON, never interpolated into arguments. When
argv is also set, this string is not executed through the shell.list[string]
optional
Execute an argument list directly instead of interpreting
command through a shell. Use this for explicit executable paths and arguments.number
optional
Per-handler timeout in seconds. The default is 600 seconds, except for
UserPromptSubmit, which defaults to 30 seconds. A timeout is a non-blocking failure.string
optional
Transient message shown in the UI while the handler runs.
"async": true produces a visible configuration error.
Handler environment
A handler starts in the working directory reported ascwd in the payload and inherits the session environment with credential-looking variables removed: any name containing KEY, TOKEN, SECRET, PASSWORD, or APIKEY is stripped before launch. A handler that needs a credential must read it from a file or a secret manager rather than the inherited environment. Plugin handlers additionally receive their own plugin path variables.
Matchers
A matcher filters whether a handler group runs for a given event. Each event matches against one field (see Events):- Omitted, empty, or
*matches all values for that event. - A simple name matches exactly (
Bash). |or,separates exact alternatives (Edit|Write).- Any other value is treated as an unanchored regular expression (
mcp__.*).
UserPromptSubmit and Stop have no matcher field. Omit matcher for those events, or set it to *; any other value is rejected when the configuration loads.
Compile errors invalidate that group and produce a user-visible configuration diagnostic before the session runs.
Events
Deep Agents Code emits the following events. Client-owned events run in the CLI process. Server-owned events originate in the agent execution path and round-trip to the client so command handlers run where your configuration lives.PreToolUse runs before the permission prompt and before tool execution, which makes it the primary place to allow or deny tools. Stop runs before a terminal model response is committed.
The diagram covers the tool-call path only. PermissionRequest is a separate client-owned event when Deep Agents Code is about to show a permission prompt.
Input payload
Every handler receives a JSON object on stdin. All events share a common envelope, plus event-specific fields.Common fields
transcript_path points to a JSONL projection of the conversation written under ~/.deepagents/transcripts. Subagent events also carry agent_transcript_path for the subagent’s own transcript. Both files are refreshed before matching handlers run, so a handler can read the conversation up to the current event.
Event-specific fields
Example
PreToolUse payload:
Tool names
Hook scripts see stable public tool names and argument shapes, not internal Deep Agents Code tool names. Match on and read these names inPreToolUse, PostToolUse, and PermissionRequest:
Handler output
Command handlers communicate results through their exit code, stdout, and stderr.
JSON output is only processed on exit
0 and must be the only content on stdout. Successful non-JSON stdout becomes additional context for SessionStart and UserPromptSubmit; for other events it produces a diagnostic. Stdout and stderr are each retained up to 100,000 bytes.
Universal output fields
Any handler may return these top-level fields:"continue": false marks the reduced decision as stopped but does not prevent other matching handlers from running. The first stopReason wins in configuration order. suppressOutput suppresses only that handler’s systemMessage.
Event-specific control lives in hookSpecificOutput (for tool and permission events) or in top-level decision and reason (for Stop).
Control tool execution with PreToolUse
Return a permission decision to allow, deny, or force a prompt before a tool runs:
deny > ask > allow. A deny short-circuits execution before the permission prompt and feeds its reason to the model. An ask forces the permission prompt. An allow suppresses the ordinary prompt but does not override a separate deny or ask. Any additionalContext values are passed through in configuration order.
You can also block with exit code 2 and write the reason to stderr.
Allow or deny with PermissionRequest
Return a decision to answer a permission prompt on the user’s behalf:
Continue a turn with Stop
Return a block decision to keep the agent working instead of ending the turn:
Stop.hookSpecificOutput.additionalContext has the same continuation effect. To avoid infinite loops, check stop_hook_active in the payload and stop blocking once your condition is met. Deep Agents Code also enforces a hard cap of eight consecutive continuations.
Inject context
SessionStart, UserPromptSubmit, and SubagentStart can add context for the model:
UserPromptSubmit also supports suppressOriginalPrompt. PostToolUse and SubagentStop can append additionalContext for the model but cannot undo an action that already ran.
Unsupported output fields
The following compatibility fields are recognized but not applied. Deep Agents Code emits a diagnostic and continues with the fallback in the Result column. For tool and permission rows, that means the ordinary PreToolUse or PermissionRequest decision path, without mutating tool input or deferring.Examples
Block destructive Bash commands (PreToolUse)
Block destructive Bash commands (PreToolUse)
~/.deepagents/hooks/block-rm.sh
~/.deepagents/hooks.json
Load project context on session start (SessionStart)
Load project context on session start (SessionStart)
~/.deepagents/hooks/load-context.sh
~/.deepagents/hooks.json
Desktop notification when the turn ends on macOS (Stop)
Desktop notification when the turn ends on macOS (Stop)
~/.deepagents/hooks.json
Python handler that reads the payload
Python handler that reads the payload
~/.deepagents/hooks/handler.py
~/.deepagents/hooks.json
Troubleshoot hooks
Hook activity is visible in the session, not only in logs:- A running handler shows its
statusMessage, orRunning <event> hookwhen it sets none. Concurrent handlers share one status slot, so the most recent one is displayed until it finishes. - A handler’s
systemMessageappears as an informational notice. - Configuration errors, nonzero exits, timeouts, and unsupported output fields appear as
Hook warningorHook errornotices, once per invocation. - A permission answer from a hook is attributed to the hook, for example
PermissionRequest hook denied Bash. - Set
DEEPAGENTS_CODE_DEBUG=1to capture every diagnostic, including debug-level entries that are never shown as notices.
Legacy configuration
Older list-shapedhooks.json files are deprecated but still supported. Deep Agents Code automatically migrates equivalent events; events without a safe mapping are skipped with a diagnostic.
Security
Hooks follow the same trust model as Git hooks or shell aliases: any process that can write tohooks.json can run arbitrary commands with your permissions.
- Payload data flows to stdin as JSON, never interpolated into command arguments.
- Credential-looking environment variables are stripped from handler environments.
- Hook configuration remains fixed until
/reloador a new session. - Prefer explicit shell executables you control over shell wrappers.
- Only install hooks from sources you trust.
See also
Connect these docs to Claude, VSCode, and more via MCP for real-time answers.

