baa-conductor


baa-conductor / bugs / archive
codex@macbookpro  ·  2026-03-27

BUG-008-codexd-second-thread-turn-timeout.md

  1# BUG-008: codexd 第二个 session/thread 发 turn 后 app-server 响应超时,反复 reconnect 直至 failed
  2
  3## 当前状态(2026-03-25)
  4
  5按当前基线与回归测试,这个问题已随 `BUG-010` 一并修复,不再作为独立未解问题保留。
  6
  7新的 `apps/codexd/src/index.test.js` 回归测试覆盖了:
  8
  9- session A 正常完成
 10- session B 收到 `Reconnecting... 2/5` / `willRetry=true`
 11- session B 的最终 `turn/completed` 以无换行尾包到达
 12- codexd 仍然把 `lastTurnStatus` 正确收口到 `completed`
 13
 14同时,`codexd` 现在会把“合法 `turn/completed` 根本没到,就先 transport close”的场景单独分类:
 15
 16- recent events 先记 `app-server.transport.closed`
 17- 若关闭时还有 `currentTurnId`,再记 `app-server.turn.completed.missing`
 18- `detail.failureClass = "transport_closed_before_turn_completed"`
 19
 20这类诊断不再归到 `BUG-008` 21
 22## 现象
 23
 24向 codexd 创建第一个 session 并发 turn,成功完成(turn.completed)。
 25
 26创建第二个 session(新 threadId)并发 turn,消息被 app-server 接收(item/started + item/completed),但此后等待响应时超时,触发 Reconnecting... N/5,最终 failed。
 27
 28第一次 turn 的完整回复已在 events.jsonl 里出现(seq 55,agentMessage 完整),证明链路本身通;第二次 turn 卡在等 app-server 返回内容阶段。
 29
 30## 触发路径
 31
 32```text
 33POST /v1/codexd/sessions  -> session A (thread A) 创建成功
 34POST /v1/codexd/turn      -> turn 成功,Codex 回复,turn.completed
 35
 36POST /v1/codexd/sessions  -> session B (thread B) 创建成功
 37POST /v1/codexd/turn      -> item/started + item/completed(消息已发)
 38                          -> 等待 ~30s
 39                          -> Reconnecting... 2/5
 40                          -> Reconnecting... 3/5  ...
 41                          -> Reconnecting... 5/5
 42                          -> lastTurnStatus: failed
 43```
 44
 45## 根因
 46
 47根因现已收口为 codexd 侧的同一个 transport 收尾缺陷:
 48
 49- 第二个 session 的 turn 在 `willRetry=true` 之后,最终完成事件可能落在 stdio 流尾
 50- 旧实现如果尾部消息没有换行,就会在 `stdout end` / child close 前直接丢掉这条 `turn/completed`
 51- 上游看到的就会是“第二个 session 一直 retry、最后 failed 或挂住”,看起来像多 thread 竞争,实际是 codexd 没把最终完成态收进来
 52
 53目前没有新的证据表明还存在一个独立的 app-server 多 thread 状态机问题。
 54
 55## 复现步骤
 56
 57```bash
 58# 1. 创建 session A,发 turn,等 turn.completed
 59curl -X POST http://127.0.0.1:4319/v1/codexd/sessions \
 60  -H 'Content-Type: application/json' \
 61  -d '{"purpose":"duplex"}'
 62
 63curl -X POST http://127.0.0.1:4319/v1/codexd/turn \
 64  -H 'Content-Type: application/json' \
 65  -d '{"sessionId":"<sessionId_A>","input":"你是谁?"}'
 66
 67# 等 turn.completed 后,创建 session B
 68curl -X POST http://127.0.0.1:4319/v1/codexd/sessions \
 69  -H 'Content-Type: application/json' \
 70  -d '{"purpose":"duplex"}'
 71
 72curl -X POST http://127.0.0.1:4319/v1/codexd/turn \
 73  -H 'Content-Type: application/json' \
 74  -d '{"sessionId":"<sessionId_B>","input":"1+1等于几?"}'
 75
 76# 轮询,预期出现 lastTurnStatus: failed + Reconnecting... N/5
 77curl http://127.0.0.1:4319/v1/codexd/sessions/<sessionId_B>
 78```
 79
 80## 修复前影响
 81
 82- 单次会话(只建一个 session)可以正常完成,链路通
 83- 多 session 场景(顺序创建多个 thread)大概率失败
 84- 影响 codexd 作为 duplex 对话代理的实用性,每次对话理论上都会建新 session/thread
 85
 86## 修复建议
 87
 88本条已由 `方案 C` 的 transport 修复覆盖,不需要为此额外引入 thread 复用或显式 thread close 逻辑。
 89
 90## 严重程度
 91
 92High —— 单次对话能通,但多轮/多 session 场景(即 duplex 核心用法)无法稳定工作。
 93
 94## 发现时间
 95
 962026-03-23 by Claude
 97
 98## 备注
 99
100- events.jsonl seq 55 完整记录了第一次 turn 的回复,Codex 回复内容:「我是 Codex,一个在你的工作区里直接读代码、改代码、跑命令并帮你解决工程问题的 AI 编程助手。」
101- seq 59 turn.completed 出现,第一次链路完全通
102- 第二次失败的 error detail: codexErrorInfo.responseStreamDisconnected.httpStatusCode = null,additionalDetails = "timeout waiting for child process to exit"
103- child 进程(PID 20904)在失败后仍处于 running 状态,不是 crash
104
105## Reopen 规则
106
107下面这些仍属于已修复范围,不 reopen `BUG-008`108
109- recent events 能看到该 turn 的 `app-server.turn.completed`
110- 即使 completed 落在无换行尾包,session 最终仍收口到 `lastTurnStatus: completed`
111- 中间出现 `Reconnecting... N/5` / `willRetry=true`,但最终 completed 到达
112
113下面这些应新开 bug,不算 `BUG-008` 复发:
114
115- `app-server.transport.closed` 出现后,该 turn 没有任何合法 `app-server.turn.completed`
116- `codexd` 追加 `app-server.turn.completed.missing`
117- 事件 detail 标明 `failureClass = "transport_closed_before_turn_completed"`
118
119## 剩余风险
120
121当前代码与测试已经把“尾包无换行但 completed 实际发出”和“transport 提前断流导致 completed 缺失”拆成两类;后一类如果在线上再次出现,应作为新的 child / transport 故障单独跟踪。