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 故障单独跟踪。