Skip to main content
Hooks let external programs observe and control Deep Agents Code lifecycle events. When an event fires, Deep Agents Code finds matching handlers, sends each a JSON payload on stdin, and combines their exit codes and stdout. Use that response to allow, deny, inject context, or continue a turn. The sections below cover configuration, Events, Input payload, and Handler output. Hooks run with your user permissions and execute arbitrary code from your configuration. Treat hook configuration as executable code and only install hooks from sources you trust.

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
Deep Agents Code loads hook configuration in precedence order:
  1. Project hooks from {project_root}/.deepagents/hooks.json, after workspace trust is granted.
  2. User hooks from ~/.deepagents/hooks.json.
  3. Hooks contributed by enabled plugins.
Every matching handler runs concurrently, and their results are combined in precedence order. Precedence decides which answer wins, not which handlers execute: a lower-precedence handler still runs, and its side effects still happen, even when a higher-precedence handler stops the event. Run 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 Esc or Ctrl+D aborts startup.
  • Headless and CI runs never prompt. Pass --trust-project-hooks to opt in for that run.

Plugin hooks

An enabled plugin contributes the same configuration shape from hooks/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’s hooks 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.
Configuring an unsupported handler type or "async": true produces a visible configuration error.

Handler environment

A handler starts in the working directory reported as cwd 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 in PreToolUse, 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:
All matching handlers finish before their results are combined. Returning "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:
When multiple hooks match, decisions combine with the precedence 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:
Any deny wins. If no hook denies and at least one allows, the action is allowed. If no hook decides, the normal permission prompt is shown.

Continue a turn with Stop

Return a block decision to keep the agent working instead of ending the turn:
A block continues the agent turn with your feedback. 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

~/.deepagents/hooks/block-rm.sh
~/.deepagents/hooks.json
~/.deepagents/hooks/load-context.sh
~/.deepagents/hooks.json
~/.deepagents/hooks.json
~/.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, or Running <event> hook when it sets none. Concurrent handlers share one status slot, so the most recent one is displayed until it finishes.
  • A handler’s systemMessage appears as an informational notice.
  • Configuration errors, nonzero exits, timeouts, and unsupported output fields appear as Hook warning or Hook error notices, once per invocation.
  • A permission answer from a hook is attributed to the hook, for example PermissionRequest hook denied Bash.
  • Set DEEPAGENTS_CODE_DEBUG=1 to capture every diagnostic, including debug-level entries that are never shown as notices.

Legacy configuration

Older list-shaped hooks.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 to hooks.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 /reload or a new session.
  • Prefer explicit shell executables you control over shell wrappers.
  • Only install hooks from sources you trust.
A hook runs with your user permissions. Treat hook configuration as executable code.

See also