~/.deepagents/hooks.json, and Deep Agents Code sends a JSON payload to each matching handler whenever an event fires. Some events also read the handler’s response to allow, deny, or continue an action.
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. Handlers nest in three levels: event name, matcher group, then the handlers that run for that group.
~/.deepagents/hooks.json
dcode config path to confirm the resolved location of hooks.json and whether it exists.
Hook configuration is snapshotted when a session starts. Editing hooks.json mid-session does not add, remove, or weaken hooks until the next session, so an agent that edits settings during a turn cannot change the policy hooks that govern it.
Handler fields
Each entry in a matcher group’shooks array is a command handler:
Handler type. Only
command is supported. A command handler runs a subprocess that receives the event JSON on stdin.Path to the executable or script to run. The event payload is written to stdin as JSON, never interpolated into arguments.
Per-handler timeout in seconds. Handlers that exceed the timeout are cancelled and treated as a non-blocking failure.
Message shown in the UI while the handler runs.
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). - Alternation matches either side (
Edit|Write). - Any other value is treated as a regular expression (
mcp__.*).
PreToolUse, PostToolUse, PermissionRequest) also accept an optional if predicate using permission-rule syntax, such as Bash(git *), as a second-stage filter. On any other event, an if predicate fails closed and the handler does not run.
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 enforcement seam. Stop runs before a terminal model response is committed.
Input payload
Every handler receives a JSON object on stdin. All events share a common envelope, plus event-specific fields.Common fields
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 a JSON response must be the only content on stdout. User-facing and model-facing output is capped at 10,000 characters.
Universal output fields
Any handler may return these top-level fields:"continue": false stops all processing and takes precedence over event-specific decisions. When multiple hooks match, the first stopReason in configuration order is shown, and all systemMessage values are preserved in configuration order.
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_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 with start events
SessionStart and SubagentStart cannot block, but they can add context for the model:
PostToolUse and SubagentStop are also non-blocking: they can append additionalContext for the model but cannot undo an action that already ran.
Unsupported output fields
Some fields defined by the hook output contract are recognized and validated, but not applied. When a handler returns one of these, Deep Agents Code emits a diagnostic and falls back to normal behavior rather than failing silently: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 (Stop)
Desktop notification when the turn ends (Stop)
~/.deepagents/hooks.json
Python handler that reads the payload
Python handler that reads the payload
~/.deepagents/hooks/handler.py
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.
- Hook configuration is snapshotted at session start, so a mid-session settings change cannot alter the active policy hooks.
- Prefer explicit executables and argument arrays 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.

