跳到主要内容
路径文档

Hooks 桥(Claude / Codex)

一句话版:DSH 的核心扩展面是类型化拦截点(native 插件就够);hooks-claude-code/hooks-codex桥插件:把你已有的 Claude Code / Codex 的 hooks.json shell 钩子,原样跑在 DSH 的这些拦截点上,兼容旧生态。

如果你以前用 Claude Code / Codex 配了一批 shell 钩子(pre-commit、门禁、通知…),不想在 DSH 里重写,dsh-hooks-* 能让它们继续跑。

一、两个概念,别混

native 插件(推荐)hooks 桥(兼容)
是什么挂在 DSH 类型化拦截点上的普通 Cordis 插件翻译"外部 shell-hook 协议"到同一拦截点的桥
体验类型化返回、无序列化边界走子进程 shell 钩子
何时用新写的扩展已有 Claude/Codex hook 配置想兼容

源码原话:native 插件能更强大地做到桥做的一切。桥只作为兼容路径存在;凡是新写的东西,都应该做 native 插件。

二、包

角色
dsh-hook-protocol共享 shell-hook wire 协议(matcher、stdlib codec、ctx.shell 执行、hook/* 事件)
dsh-hooks-claude-codeClaude Code 钩子桥(CC 方言:stdin 载荷、${CLAUDE_PLUGIN_ROOT}/${CLAUDE_PROJECT_DIR} 替换)
dsh-hooks-codexCodex 钩子桥(Codex 方言)

hook-protocol 不是插件:不注册、不注入任何东西,只是两个桥共用的库。

三、配置:接一个 Claude hooks.json

# cordis.yml / patch
- id: hooks-claude-code
name: '@deepseek-ai/dsh-hooks-claude-code'
config:
configPath: ./.claude/hooks.json # 必填:hooks.json 或带 hooks 键的 settings
pluginRoot: ./.claude/plugins/my # 选填:替换 ${CLAUDE_PLUGIN_ROOT}
projectDir: . # 选填:替换 ${CLAUDE_PROJECT_DIR};缺省 = session cwd
defaultTimeoutMs: 600_000 # 选填:钩子没设超时时的默认(CC 默认)
stderrSummaryMaxChars: 500 # 选填:hook/result 事件持久化 stderr 摘要的字符上限

Codex 桥配置形状相同,多一个可选 model(打在每条 stdin 载荷上),configPath 通常指向 ./.codex/hooks.json

  • 支持 CC/Codex 支持的 command-hook 子集;映射:钩子中性结果 → harness 类型化 Decisions(allow/deny/ask 等);hook/* 会话事件记录钩子执行
  • 配置只在装载时解析一次,configPath进程级的(相对路径按启动 cwd 解析,没有按 session 的发现);读取/解析失败隔离(记日志、注册零钩子)
  • 只有 type: 'command' 的 shell 钩子会跑,http/mcp_tool/prompt/agent 等 handler 解析后跳过并警告

四、hook 协议形状

两个桥共享一套协议原语,各自只管方言差异:

关注点共享库(dsh-hook-protocol)桥(-claude / -codex)
matcher 校验/匹配matcherDiagnostic / matchesMatcher选 mode:claude = 字面量或正则,codex = 恒为正则
跑钩子runHook:stdin 载荷 + env 经 ctx.shell构造每事件 stdin 载荷 + 方言 env
解码/合并parseHookOutputHookOutput;mergeHookOutputs → 最严格结果把中性结果映射成拦截点类型化 Decision
持久记录hook/invoked / hook/result 会话事件围绕每次调用调用它们

关键语义:

  • exit code 2 = 阻塞(带 stderr);其他失败是非阻塞错误。
  • 合并取最严格:deny > ask > allow;continue:false 首现即粘住;additionalContext/systemMessages 按序累积。
  • matcher:claude 模式纯 [A-Za-z0-9_|]+ 当字面量(管道 = 精确交替),其余当正则;codex 模式恒为无锚正则。
  • hook/* 事件是 log-only(像 compaction/*),非 SurfaceEventType:配对 hook/invokedhook/result,stderr 摘要截到 stderrSummaryMaxChars

五、拦截点 → 决策映射

Claude 桥把 CC 钩子映射到 DSH 拦截点:

CC 钩子DSH 拦截点映射
SessionStartagent/session-start(emit)additionalContext → agent.inject() 进新会话
UserPromptSubmitagent/pre-step(waterfall)deny → reject;仅 additionalContext → next() 委托后附加上下文
PreToolUsetools/pre-execute(waterfall)deny → deny;ask → ask
PostToolUsetools/post-execute(waterfall)deny → block + feedback
Stopagent/turn-stopping(serial)阻塞的 Stop 经 steer() 强推下一步
SubagentStartsubagent/start(emit)additionalContext → 注入活的进程内子 agent
SubagentStopsubagent/end(emit)只观察

Codex 桥实现 10 个点里的 5 个(PreToolUse/PostToolUse/SessionStart/UserPromptSubmit/Stop),用 block(exit 2)代替 deny,PreToolUseallow/ask

  • emit 点(SessionStart/SubagentStart/SubagentStop)分离运行,没有拦截点在等;运行链被追踪,dispose 时先中止仍在跑的钩子进程、再 drain 续体。
  • matcher 主体:工具名(PreToolUse/PostToolUse)、会话来源(SessionStart)、常量 agent_type=general-purpose(SubagentStart/SubagentStop);UserPromptSubmit/Stop 忽略 matcher。
  • 同一拦截点上的多个钩子按配置顺序串行执行并折叠最严格。

六、与 tools 门禁的区别

两者最终落在同一些拦截点上,但别混(口径与 监听事件 一致):

native 门禁(推荐)hooks 桥
实现ctx.on('tools/pre-execute', ...) 返回 PreToolDecision把外部 shell 钩子的 stdout/exit code 翻译成同一决策
类型类型化返回值,无序列化边界经子进程 + stdin/stdout 序列化
何时用在 DSH 上配新门禁/拦截已有 Claude/Codex hooks.json 想兼容

七、什么时候用

场景
已有大量 Claude/Codex 钩子,想无缝迁移hooks 桥
在 DSH 上配新门禁/拦截native 插件(tools 流水线、agent 事件)
@dsh-external 社区钩子按插件装

八、验证

# 看 hooks 桥是否装载
dsh web --dump-config | grep -iE "hook"
# 会话里看 hook 执行
zstdcat ~/.dsh/sessions/*/*/session.jsonl.zstd | grep -E '"hook/' | head

下一步