Skip to content

fix(agent): claude/codex run 假死根治——事件流必出终态 + 看门狗硬出口 + 进程树清理 (closes #9) - #10

Open
alone-lon wants to merge 1 commit into
monkey2jack:mainfrom
alone-lon:fix/issue-9-stream-hang
Open

alone-lon wants to merge 1 commit into
monkey2jack:mainfrom
alone-lon:fix/issue-9-stream-hang

Conversation

@alone-lon

Copy link
Copy Markdown
Contributor

Summary

修复 #9(2026-11-08 claude run 假死:子进程快速退出后事件流挂死,看门狗无法闭环)。claude.exe 偶发在产出终态事件前快速退出,而它的孙进程继承了 stdout 写句柄——事件流的 readline 永远等不到 EOF,for await 永久挂起;此时 idle-watchdog 调的 stop() 对已死进程是空操作,inflight 槽位冻结,该 chat 所有新消息只能排队,只能重启恢复。

三层修复,与 issue 里的 P0/P1/P2 一一对应:

  • P0 事件流退出兜底(claude + codex adapter):监听 child exit,无条件宽限 2 秒(留尾行 flush)后 destroy stdout 强制 EOF,走已有的 exitCode→yield error 路径。原兜底 closeSilentStdout 只在 !sawStdout 时生效,正常 run 的 init 行早已输出,形同虚设。另外 claude 退出码 0 但从未产出 result 事件时补发合成 error,保证任何情况下事件流必出终态(codex 原有 translator.finish() 兜底不受影响)。
  • P1 看门狗硬出口(run-executor + channel):EventFanout 新增 abort(),能打断卡死的 pump(race 掉 pending 的 next()),注入合成 error 终态;execution.forceClose() = abort + 杀进程树。看门狗开火 10 秒后 run 仍未到终态即触发,error 级日志留证据。原则:任何 run 必须存在一条与事件流是否配合无关的强制出口。
  • P2 进程树管理:新增 src/platform/kill-tree.ts(Windows taskkill /T /F——child.kill() 杀不到 .cmd shim 后面的真身和孙进程;POSIX 保持 SIGKILL)。接入三处:adapter stop() 宽限到期、executor waitForPreviousExit 超时分支(原来只删 map 不杀进程——issue 里那个悬空两小时的孤儿就是这么来的)、forceClose。

AgentRun 接口新增 pid 与 forceKillTree();hermes adapter 同步实现接口(行为不变)。P3 观测随 P1 落地(stdout-force-close-after-exit、watchdog-force-close、previous-exit-timeout 均有日志)。

Test plan

  • 新增:claude/codex 各一个「立即退出 + 孙进程持有 stdout 写端 8 秒」进程级用例——旧代码挂死,现在事件流 <7s 出终态 error(实测 ~0.5s)
  • 新增:claude 退出码 0 无 result 事件 → 合成 error 终态
  • 新增:forceClose 硬出口单测(合成终态、槽位释放、杀树、幂等);previous-exit-timeout 走杀树路径单测
  • 新增:Windows 真实 taskkill /T /F 树杀测试(父+孙进程全灭)
  • 回归:全量 1218 测试与改动前基线逐条对比,0 新增失败(23 个既有失败为 Windows 环境所致,基线同样失败)
  • pnpm typecheck、pnpm build 通过

备注

  • 分支已 rebase 到上游最新 main(含 holdEmptyDone,与本次 pid 字段已合并、测试全绿)
  • 不影响现网运行版本:修复在下次重启 aiduCAT 时生效;正常 run 的行为路径完全不变
  • 后续可选项(不在本 PR 范围):Windows Job Object 绑定进程树、codex/claude 之外的 agent 面扩展

🤖 Generated with Claude Code

…nkey2jack#9)

claude/codex runs could freeze a chat slot for hours when the CLI exited
fast without a terminal event: a grandchild inheriting the stdout write
handle kept the readline alive, so the event stream never ended and the
idle watchdog's stop() was a no-op on an already-dead child (2026-11-08
incident, issue monkey2jack#9).

- adapters: unconditional stdout destroy 2s after child exit (was: only
  when nothing had been printed); synthesize an error event when claude
  exits without a result event
- adapters: stop() now escalates to a process-tree kill (taskkill /T /F
  on Windows — child.kill() cannot reach past the .cmd shim); new
  AgentRun.forceKillTree() works even after the child exited
- executor: EventFanout.abort() ends a wedged stream with a synthetic
  terminal error; execution.forceClose() = abort + tree kill, wired to
  the watchdog firing 10s after stop() failed to end the run
- executor: previous-exit-timeout now tree-kills the stuck run instead
  of leaking it as an orphan holding its session file open
- channel: watchdog escalation logs at error level with the reason
- process-tree kill in src/platform/kill-tree.ts, shared by all three
  agents (hermes included for the interface); POSIX keeps plain SIGKILL

Tests: quick-exit-with-grandchild coverage for claude and codex (stream
ends <7s with a terminal error), forceClose hard-exit and drain-timeout
tree-kill unit tests, a real taskkill /T /F tree test on Windows.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
monkey2jack added a commit that referenced this pull request Nov 13, 2026
Restore stuck streams after the agent process exits, preserve buffered output and retry watchdog recovery without prematurely releasing a running process. Synchronize the Chinese README banner.

Based on issue #9 and PR #10 by @alone-lon. Windows descendant process cleanup remains to be investigated.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant