主题
Hooks(钩子)
通过在 hooks.json 中配置的命令钩子来观察和控制 Deep Agents Code 的生命周期事件
钩子让外部程序能够观察和控制 Deep Agents Code 的生命周期事件。
当事件触发时,Deep Agents Code 会查找匹配的处理程序,通过 stdin 向每个处理程序发送 JSON 载荷,并合并它们的退出码与 stdout。利用该响应可以允许、拒绝、注入上下文或继续一轮对话。以下各节涵盖配置、事件、输入载荷 和 处理程序输出。
钩子以你的用户权限运行,并执行配置中的任意代码。请将钩子配置视为可执行代码,只安装来自你信任来源的钩子。
设置
创建 ~/.deepagents/hooks.json 可定义适用于所有项目的钩子,创建 {project_root}/.deepagents/hooks.json 可定义项目范围内的钩子(需先授予工作区信任)。处理程序分为三层嵌套:事件名称、匹配器组,以及为该组运行的处理程序。
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.deepagents/hooks/block-rm.sh",
"timeout": 600
}
]
}
],
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "~/.deepagents/hooks/load-context.sh"
}
]
}
]
}
}Deep Agents Code 按优先级顺序加载钩子配置:
- 来自
{project_root}/.deepagents/hooks.json的项目钩子,在授予工作区信任之后生效。 - 来自
~/.deepagents/hooks.json的用户钩子。 - 由已启用插件提供的钩子。
每个匹配的处理程序都会并发运行,其结果按优先级顺序合并。优先级决定哪个结果胜出,而不决定哪些处理程序会执行:即使优先级较高的处理程序终止了该事件,优先级较低的处理程序仍会运行,其副作用依然会发生。
运行 dcode config path 可查看相互独立的项目钩子位置与用户钩子位置,以及工作区信任存储。
钩子配置会被快照化,直到执行 /reload 或开启新会话才会更新。在一轮对话期间编辑 hooks.json 不会改变当前生效的快照。启用或禁用插件同样会改变快照,因此请运行 /reload 以加载其钩子。
信任项目钩子
项目钩子来自代码仓库,因此只有在工作区被信任之后才会加载:
- 当不受信任的工作区包含
.deepagents/hooks.json时,交互式会话会弹出提示。信任该工作区会把这一决定按项目根目录持久化保存到~/.deepagents/.state/hooks_trust.json。 - 拒绝该提示会跳过该会话的项目钩子,并继续使用用户钩子和插件钩子。
- 使用
Esc或Ctrl+D取消该提示会中止启动。 - 无头(headless)运行和 CI 运行不会弹出提示。可传入
--trust-project-hooks为该次运行选择启用。
插件钩子
已启用的插件可以通过 hooks/hooks.json、清单中的 hooks 路径或内联的清单对象提供相同结构的配置。安装并启用插件即是同意授权:工作区信任只管控项目钩子,因此它既不会授予也不会拒绝插件的钩子。启用插件前请先审阅它,并在插件管理器中检查它声明的事件。参见插件与市场。
服务端持有的事件在会话启动时就已确定,因此新启用的插件钩子会在下次启动或执行 /reload 时生效。
插件处理程序可以通过以下变量引用其自身的安装路径:
| 变量 | 值 |
|---|---|
${CLAUDE_PLUGIN_ROOT}、${PLUGIN_ROOT} | 已安装插件的目录 |
${CLAUDE_PLUGIN_DATA}、${PLUGIN_DATA} | 插件的可写数据目录 |
${CLAUDE_PROJECT_DIR} | 项目根目录 |
请在 command 字符串中为这些变量加引号,因为安装路径可能包含空格:"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/format.sh\""。在可行的情况下请优先使用可选的 argv 字段:Deep Agents Code 会在启动前解析这些变量并跳过 shell,因此你无需加引号。
无效的插件钩子文档会被单独跳过,并作为配置诊断信息上报。其他插件、项目钩子和用户钩子仍会继续工作。
处理程序字段
匹配器组中 hooks 数组的每一项都是一个命令处理程序:
string必填
处理程序类型。仅支持 command。命令处理程序会运行一个子进程,该子进程通过 stdin 接收事件 JSON。
string必填
要运行的 shell 命令。始终必填。支持管道、重定向、通配符和环境变量展开。事件载荷以 JSON 形式写入 stdin,绝不会插值到参数中。当同时设置 argv 时,该字符串不会通过 shell 执行。
list[string]
直接执行参数列表,而不是通过 shell 解释 command。当需要明确指定可执行文件路径和参数时使用此方式。
number
单个处理程序的超时时间(以秒为单位)。默认值为 600 秒,但 UserPromptSubmit 例外,其默认值为 30 秒。超时属于非阻塞性失败。
string
处理程序运行期间在 UI 中显示的临时消息。
配置不受支持的处理程序类型或 "async": true 会产生可见的配置错误。
处理程序环境
处理程序在载荷中 cwd 所指示的工作目录中启动,并继承会话环境,但其中形似凭证的变量会被移除:任何名称中包含 KEY、TOKEN、SECRET、PASSWORD 或 APIKEY 的变量都会在启动前被剔除。需要凭证的处理程序必须从文件或密钥管理器中读取,而不能依赖继承来的环境。插件处理程序还会额外获得其自身的插件路径变量。
匹配器
匹配器用于筛选某个处理程序组是否针对给定事件运行。每个事件针对一个字段进行匹配(参见事件):
- 省略、为空或
*会匹配该事件的所有取值。 - 简单名称按精确匹配(
Bash)。 |或,用于分隔精确的备选项(Edit|Write)。- 其他任何取值都会被视为非锚定的正则表达式(
mcp__.*)。
UserPromptSubmit 和 Stop 没有匹配器字段。对于这些事件,请省略 matcher 或将其设为 *;配置加载时,任何其他取值都会被拒绝。
编译错误会使该组失效,并在会话运行前产生用户可见的配置诊断信息。
事件
Deep Agents Code 会发出以下事件。客户端持有的事件在 CLI 进程中运行。服务端持有的事件源自智能体执行路径,并回传到客户端,从而使命令处理程序在你的配置所在位置运行。
| 事件 | 归属方 | 退出码 2 的效果 | 匹配字段 |
|---|---|---|---|
SessionStart | 客户端 | 诊断信息 | source |
UserPromptSubmit | 客户端 | 阻止提示词 | 无 |
SessionEnd | 客户端 | 诊断信息 | reason |
PermissionRequest | 客户端 | 拒绝 | tool_name |
Notification | 客户端 | 诊断信息 | notification_type |
PreToolUse | 服务端 | 拒绝 | tool_name |
PostToolUse | 服务端 | 反馈 | tool_name |
PreCompact | 服务端 | 阻止压缩 | trigger |
Stop | 服务端 | 继续本轮 | 无 |
SubagentStart | 服务端 | 诊断信息 | agent_type |
SubagentStop | 服务端 | 追加上下文 | agent_type |
PreToolUse 在权限提示之前、工具执行之前运行,这使它成为允许或拒绝工具的主要位置。Stop 在最终的模型响应被提交之前运行。
flowchart LR
A["Agent requests tool"] --> P["PreToolUse"]
P -->|allow| T["Tool runs"]
P -->|ask| H["Permission prompt"]
P -->|deny| X["Tool blocked"]
H --> T
T --> PT["PostToolUse"]
classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
classDef decision fill:#FDF3FF,stroke:#7E65AE,stroke-width:2px,color:#504B5F
classDef alert fill:#F8E8E6,stroke:#B27D75,stroke-width:2px,color:#634643
class A,T,PT process
class P,H decision
class X alert
该图仅涵盖工具调用路径。当 Deep Agents Code 即将显示权限提示时,PermissionRequest 是一个独立的客户端持有事件。
输入载荷
每个处理程序都会通过 stdin 收到一个 JSON 对象。所有事件共享一个公共的信封结构,此外还有各事件特有的字段。
公共字段
| 字段 | 说明 |
|---|---|
session_id | 会话标识符 |
transcript_path | 对话记录文件的路径(若可用) |
cwd | 钩子被调用时的工作目录 |
hook_event_name | 所触发事件的名称 |
prompt_id | 当前用户提示词的 UUID(若可用) |
permission_mode | 权限模式(default、plan、acceptEdits、auto、dontAsk、bypassPermissions),在有意义时提供 |
effort | 形如 { "level": "medium" } 的对象,其中 level 为 none、low、medium、high、xhigh 或 max(若可用) |
agent_id、agent_type | 子智能体身份标识(若可用) |
transcript_path 指向写入 ~/.deepagents/transcripts 下的对话 JSONL 投影文件。子智能体事件还会携带 agent_transcript_path,指向子智能体自身的对话记录。这两个文件都会在匹配的处理程序运行前刷新,因此处理程序可以读取到当前事件为止的对话内容。
事件特有字段
| 事件 | 字段 |
|---|---|
SessionStart | source(startup、resume、clear、compact),以及 model(若可用) |
UserPromptSubmit | prompt |
SessionEnd | reason(clear、resume、prompt_input_exit、other) |
PermissionRequest | tool_name、tool_input、permission_suggestions(当前为空) |
Notification | message、notification_type,以及 title(若可用) |
PreToolUse | tool_name、tool_input、tool_use_id |
PostToolUse | tool_name、tool_input、tool_response、tool_use_id,以及 duration_ms(若可用) |
PreCompact | trigger(manual、auto)、custom_instructions |
Stop | stop_hook_active、last_assistant_message、background_tasks、session_crons |
SubagentStart | agent_id、agent_type |
SubagentStop | stop_hook_active、agent_id、agent_type、agent_transcript_path、last_assistant_message、background_tasks、session_crons |
PreToolUse 载荷示例:
json
{
"session_id": "abc123",
"transcript_path": "/Users/you/.deepagents/.../transcript.jsonl",
"cwd": "/Users/you/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /tmp/build"
},
"tool_use_id": "toolu_01ABC"
}工具名称
钩子脚本看到的是稳定的公开工具名称和参数结构,而非 Deep Agents Code 的内部工具名称。请在 PreToolUse、PostToolUse 和 PermissionRequest 中按这些名称进行匹配和读取:
| 公开工具名称 | 重要的输入字段 |
|---|---|
Bash | command,以及可选的 timeout(以毫秒为单位) |
Write | file_path、content |
Edit | file_path、old_string、new_string、replace_all |
Read | file_path、limit、offset |
Glob | pattern、path |
Grep | pattern、path、glob、output_mode、head_limit |
LS | path |
mcp__<server>__<tool> | 各工具特有的 JSON |
处理程序输出
命令处理程序通过退出码、stdout 和 stderr 传递结果。
| 退出码 | 含义 |
|---|---|
0 | 成功。当 stdout 包含 JSON 时,会被解析并应用。 |
2 | 该事件的阻塞或反馈路径。参见事件表中的「退出码 2 的效果」列。stdout 中的 JSON 会被忽略,stderr 是主要的反馈通道。 |
| 其他非零值 | 非阻塞性错误。Deep Agents Code 会记录诊断信息并继续运行。 |
只有在退出码为 0 时才会处理 JSON 输出,且它必须是 stdout 上的唯一内容。对于成功的非 JSON stdout 内容,在 SessionStart 和 UserPromptSubmit 中会作为附加上下文;对于其他事件,则会产生一条诊断信息。stdout 和 stderr 各自最多保留 100,000 字节。
通用输出字段
任何处理程序都可以返回以下顶层字段:
json
{
"continue": true,
"stopReason": "optional user-facing reason when continue is false",
"suppressOutput": false,
"systemMessage": "optional message shown to the user",
"terminalSequence": "optional restricted terminal control sequence",
"hookSpecificOutput": {
"hookEventName": "PreToolUse"
}
}所有匹配的处理程序都会先执行完毕,然后才合并它们的结果。返回 "continue": false 会将归约后的决策标记为已停止,但不会阻止其他匹配的处理程序运行。按配置顺序,第一个 stopReason 生效。suppressOutput 只抑制该处理程序自身的 systemMessage。
事件特有的控制位于 hookSpecificOutput 中(针对工具和权限事件),或位于顶层的 decision 与 reason 中(针对 Stop)。
使用 PreToolUse 控制工具执行
返回一个权限决策,以在工具运行之前允许、拒绝或强制弹出提示:
json
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}当多个钩子匹配时,决策按 deny > ask > allow 的优先级合并。deny 会在权限提示之前短路执行,并把其原因反馈给模型。ask 会强制弹出权限提示。allow 会抑制常规提示,但不会覆盖另一个单独的 deny 或 ask。任何 additionalContext 值都会按配置顺序原样传递。
你也可以使用退出码 2 进行阻塞,并将原因写入 stderr。
使用 PermissionRequest 允许或拒绝
返回一个决策,代表用户回应权限提示:
json
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "deny",
"message": "Not allowed in this environment"
}
}
}任何一个 deny 都会生效。如果没有钩子拒绝,且至少有一个允许,则该操作被允许。如果没有钩子作出决策,则显示常规的权限提示。
使用 Stop 继续一轮对话
返回阻塞决策,让智能体继续工作,而不是结束本轮:
json
{
"decision": "block",
"reason": "Tests are still failing; keep working"
}阻塞决策会带着你的反馈继续智能体的本轮对话。Stop.hookSpecificOutput.additionalContext 具有相同的继续效果。为避免无限循环,请检查载荷中的 stop_hook_active,并在条件满足后停止阻塞。Deep Agents Code 还强制设置了连续八次继续的硬上限。
注入上下文
SessionStart、UserPromptSubmit 和 SubagentStart 可以为模型添加上下文:
json
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Current sprint: ENG-1421. Prefer the staging database."
}
}UserPromptSubmit 还支持 suppressOriginalPrompt。PostToolUse 和 SubagentStop 可以为模型追加 additionalContext,但无法撤销已经执行过的操作。
不受支持的输出字段
以下兼容性字段会被识别但不会生效。Deep Agents Code 会发出诊断信息,并按 Result 列中的回退方案继续。对于工具行和权限行,这意味着走常规的 PreToolUse 或 PermissionRequest 决策路径,不会修改工具输入,也不会延迟处理。
| 字段或行为 | 结果 |
|---|---|
SessionStart.initialUserMessage、sessionTitle、watchPaths、reloadSkills | 已解析,但不生效 |
UserPromptSubmit.sessionTitle | 已解析,但不生效 |
PreToolUse.updatedInput | 忽略修改;allow 或 ask 走常规的 PreToolUse 决策 |
PreToolUse.defer | 走常规的 PreToolUse 决策;绝不会被视为 allow |
PostToolUse.updatedToolOutput、updatedMCPToolOutput | 已解析,但不生效 |
PermissionRequest.updatedInput | 忽略修改;allow 走常规的 PermissionRequest 决策 |
PermissionRequest.updatedPermissions | 已解析,但不生效(不存在权限规则存储) |
SubagentStop 阻塞 | 仅作为上下文;已完成的子智能体无法恢复 |
示例
阻止破坏性的 Bash 命令(PreToolUse)
bash
#!/usr/bin/env bash
command=$(jq -r '.tool_input.command // ""')
if printf '%s' "$command" | grep -Eq 'rm[[:space:]]+.*-[a-zA-Z]*[rf]'; then
cat <<'JSON'
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Recursive or forced rm is blocked by policy"
}
}
JSON
fijson
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "~/.deepagents/hooks/block-rm.sh" }
]
}
]
}
}在会话开始时加载项目上下文(SessionStart)
bash
#!/usr/bin/env bash
context=$(git -C "$(jq -r '.cwd')" log --oneline -5 2>/dev/null)
jq -n --arg ctx "$context" '{
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: ("Recent commits:\n" + $ctx)
}
}'json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{ "type": "command", "command": "~/.deepagents/hooks/load-context.sh" }
]
}
]
}
}在 macOS 上于本轮结束时发送桌面通知(Stop)
json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Agent finished\" with title \"Deep Agents Code\"'"
}
]
}
]
}
}读取载荷的 Python 处理程序
python
import json
import sys
def handle(payload: dict[str, object]) -> None:
event = payload["hook_event_name"]
if event == "PreToolUse":
tool_name = payload["tool_name"]
print(f"About to run {tool_name}", file=sys.stderr)
if __name__ == "__main__":
handle(json.load(sys.stdin))json
{
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "python3 ~/.deepagents/hooks/handler.py"
}
]
}
]
}
}排查钩子问题
钩子活动在会话中即可见,而不仅仅体现在日志中:
- 正在运行的处理程序会显示其
statusMessage,若未设置则显示Running <event> hook。并发的处理程序共用一个状态位,因此会显示最近的一个,直到它执行完毕。 - 处理程序的
systemMessage会作为提示性通知显示。 - 配置错误、非零退出、超时以及不受支持的输出字段会以
Hook warning或Hook error通知的形式出现,每次调用显示一次。 - 来自钩子的权限答复会归属于该钩子,例如
PermissionRequest hook denied Bash。 - 设置
DEEPAGENTS_CODE_DEBUG=1可捕获每一条诊断信息,包括从不作为通知显示的调试级条目。
旧版配置
旧式的列表结构 hooks.json 文件已弃用,但仍受支持。Deep Agents Code 会自动迁移等价的事件;没有安全映射关系的事件会被跳过,并产生诊断信息。
安全性
钩子遵循与 Git 钩子或 shell 别名相同的信任模型:任何能够写入 hooks.json 的进程,都可以以你的权限运行任意命令。
- 载荷数据以 JSON 形式流入 stdin,绝不会插值到命令参数中。
- 形似凭证的环境变量会从处理程序的环境中剔除。
- 钩子配置在执行
/reload或开启新会话之前保持不变。 - 优先使用你可控的显式 shell 可执行文件,而不是 shell 包装脚本。
- 只安装来自你信任来源的钩子。
WARNING
钩子以你的用户权限运行。请将钩子配置视为可执行代码。