Why
深度审计确认(2026-09-02,资源泄露审计):execShell(packages/opencode/src/hook/settings.ts:1054-1094)用 Node spawn 的 timeout 选项执行用户配置的 command hook。timeout 到点只对直接子进程(/bin/sh -c 包装)发 SIGTERM;hook 命令派生的孙进程若继承 stdio 管道且比 shell 活得久,Node 的 close 事件(要求进程退出 且 stdio 流全部关闭)永不发射,Promise 永不 resolve,commandHandler 的 Effect.promise 无 AbortSignal——整个 PreToolUse/Stop 等 hook trigger 无限挂起,阻塞会话。
Scope
packages/opencode/src/hook/settings.ts 的 execShell(约 1054-1094 行)
- 消费点
commandHandler(约 1380-1384 行)
Approach
- 超时/命令结束后改用「exit 事件 + 流关闭」分离等待:
Promise.all([exited, streamsClosed]) 外加有界超时兜底,超时后对进程组 kill(复用 packages/core/src/cross-spawn-spawner.ts 已实现的 killGroup 语义:detached + 负 PID / Windows taskkill /T),kill 后仍等待 exit 完成收割
- 不改变 hook 的既有超时配置语义与 stdout/stderr 聚合行为
Acceptance
- 新增回归测试:hook 命令派生持有 stdout 管道的孙进程并超时 →
execShell 在超时窗口(非永久)内返回,孙进程被终止
- 既有 hook 测试全绿;
bun run test:dag-core 不受影响
- 用户环境实证命令(PreToolUse hook 派生
sleep 300 & 类后台进程)不再挂死 trigger
Why
深度审计确认(2026-09-02,资源泄露审计):
execShell(packages/opencode/src/hook/settings.ts:1054-1094)用 Nodespawn的timeout选项执行用户配置的 command hook。timeout到点只对直接子进程(/bin/sh -c包装)发 SIGTERM;hook 命令派生的孙进程若继承 stdio 管道且比 shell 活得久,Node 的close事件(要求进程退出 且 stdio 流全部关闭)永不发射,Promise 永不 resolve,commandHandler的Effect.promise无 AbortSignal——整个 PreToolUse/Stop 等 hook trigger 无限挂起,阻塞会话。Scope
packages/opencode/src/hook/settings.ts的execShell(约 1054-1094 行)commandHandler(约 1380-1384 行)Approach
Promise.all([exited, streamsClosed])外加有界超时兜底,超时后对进程组 kill(复用packages/core/src/cross-spawn-spawner.ts已实现的killGroup语义:detached + 负 PID / Windowstaskkill /T),kill 后仍等待 exit 完成收割Acceptance
execShell在超时窗口(非永久)内返回,孙进程被终止bun run test:dag-core不受影响sleep 300 &类后台进程)不再挂死 trigger