Skip to content
LangChain 文档版本 跟随上游 源快照 6571aec6 更新于 2026-10-08

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 按优先级顺序加载钩子配置:

  1. 来自 {project_root}/.deepagents/hooks.json 的项目钩子,在授予工作区信任之后生效。
  2. 来自 ~/.deepagents/hooks.json 的用户钩子。
  3. 由已启用插件提供的钩子。

每个匹配的处理程序都会并发运行,其结果按优先级顺序合并。优先级决定哪个结果胜出,而不决定哪些处理程序会执行:即使优先级较高的处理程序终止了该事件,优先级较低的处理程序仍会运行,其副作用依然会发生。

运行 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,指向子智能体自身的对话记录。这两个文件都会在匹配的处理程序运行前刷新,因此处理程序可以读取到当前事件为止的对话内容。

事件特有字段

事件字段
SessionStartsource(startup、resume、clear、compact),以及 model(若可用)
UserPromptSubmitprompt
SessionEndreason(clear、resume、prompt_input_exit、other)
PermissionRequesttool_name、tool_input、permission_suggestions(当前为空)
Notificationmessage、notification_type,以及 title(若可用)
PreToolUsetool_name、tool_input、tool_use_id
PostToolUsetool_name、tool_input、tool_response、tool_use_id,以及 duration_ms(若可用)
PreCompacttrigger(manual、auto)、custom_instructions
Stopstop_hook_active、last_assistant_message、background_tasks、session_crons
SubagentStartagent_id、agent_type
SubagentStopstop_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 中按这些名称进行匹配和读取:

公开工具名称重要的输入字段
Bashcommand,以及可选的 timeout(以毫秒为单位)
Writefile_path、content
Editfile_path、old_string、new_string、replace_all
Readfile_path、limit、offset
Globpattern、path
Greppattern、path、glob、output_mode、head_limit
LSpath
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
fi
json
{
  "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

钩子以你的用户权限运行。请将钩子配置视为可执行代码。

另请参阅


BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。