codex@macbookpro
·
2026-03-27
BUG-010-codexd-turn-status-stuck-inprogress.md
1# BUG-010: codexd turn 状态停留在 inProgress,未更新为 completed
2
3## 当前状态(2026-03-25)
4
5已修复。
6
7`apps/codexd/src/app-server-transport.ts` 现在会在 stdio 连接关闭前先冲刷尾部缓冲区,确保最后一条没有换行的 JSON-RPC 消息也会被交给 `CodexAppServerClient`。`apps/codexd/src/index.test.js` 新增了顺序两个 session 的回归测试,覆盖第二个 turn 先 `willRetry=true`、再以无换行尾包发出 `turn/completed` 的路径。
8
9另外,`codexd` 现在会对“没有合法 completed 就先断流”给出独立诊断:
10
11- recent events 记录 `app-server.transport.closed`
12- 若关闭时 turn 仍未完成,额外记录 `app-server.turn.completed.missing`
13- 对应 session 会从 `inProgress` 明确收口到 `failed`
14- 事件 detail 带 `failureClass = "transport_closed_before_turn_completed"`
15
16## 现象
17
18向 codexd 提交 turn 后,Codex 实际上已经产出完整回复(agentMessage 在 events.jsonl 中有记录),但 session 的 `lastTurnStatus` 始终停留在 `inProgress`,不更新为 `completed`。
19
20同时 recentEvents 里出现 `Reconnecting... N/5`,最终 turn 被标记为 `failed` 或一直挂着。
21
22已确认的 agentMessage(通过 events.jsonl 直接读取):
23- "可以,我现在能稳定工作。"
24- "1+1等于2。"
25- 其他多条完整回复
26
27说明 Codex 子进程正常生成了答案,问题在 codexd 的状态回传链路上。
28
29## 触发路径
30
31```text
32POST /v1/codex/turn { sessionId, input }
33-> accepted: true,turnId 返回
34-> app-server.turn.started 事件出现
35-> Codex 产出 agentMessage(item/completed 事件记录到 events.jsonl)
36-> Reconnecting... 2/5 开始出现
37-> turn 状态卡在 inProgress(未收到 completed 信号)
38-> 轮询 GET /v1/codex/sessions/:id 永远看到 lastTurnStatus: inProgress
39```
40
41## 根因
42
43根因已经确认:
44
45`codexd` 的 stdio transport 以前只在读到换行时才向上游交付一条 JSON-RPC 消息;如果 app-server 最后一条消息正好是没有换行的 `turn/completed`,随后 `stdout` 结束或 child 退出,transport 会直接 `onClose`,但不会先冲刷尾部缓冲区。
46
47结果就是:
48
491. agentMessage / retry error 已经到达,说明 turn 实际执行完成或接近完成
502. 最后一条 `turn/completed` 卡在 transport buffer 里,没有进入 `CodexAppServerClient`
513. `CodexdDaemon` 没收到完成事件,session 的 `lastTurnStatus` 就停在 `inProgress`
52
53这说明问题在 codexd 自己的 transport / lifecycle 收尾边界,不是 Codex 没有生成回复。
54
55## 修复落点
56
57- `apps/codexd/src/app-server-transport.ts`
58 在 `stdout end`、stream error、process error、`process exit` 这些关闭路径统一先冲刷尾部缓冲,再触发 `onClose`
59- `apps/codexd/src/index.test.js`
60 增加真实 stdio transport 回归测试,覆盖第一个 session 完成后,第二个 session 先收到 `Reconnecting... 2/5`,最终再以无换行尾包发出 `turn/completed` 的路径
61
62## 复现步骤
63
64```bash
65# 1. 创建 session
66curl -s -X POST https://conductor.makefile.so/v1/codex/sessions \
67 -H 'Content-Type: application/json' \
68 -H 'Authorization: Bearer <TOKEN>' \
69 -d '{"purpose":"duplex"}'
70# 记录 sessionId
71
72# 2. 发 turn
73curl -s -X POST https://conductor.makefile.so/v1/codex/turn \
74 -H 'Content-Type: application/json' \
75 -H 'Authorization: Bearer <TOKEN>' \
76 -d '{"sessionId":"<sessionId>","input":"你好,用一句话介绍自己"}'
77
78# 3. 持续轮询 session 状态(等 60s+)
79curl -s https://conductor.makefile.so/v1/codex/sessions/<sessionId> \
80 -H 'Authorization: Bearer <TOKEN>'
81# 预期 lastTurnStatus 卡在 inProgress 或变成 failed
82
83# 4. 直接读 events.jsonl 验证 Codex 实际已回复
84grep agentMessage /Users/george/code/baa-conductor/logs/codexd/codexd/events.jsonl \
85 | python3 -c "import sys,json; [print(json.loads(l)['detail']['params']['item']['text']) for l in sys.stdin]"
86# 预期:能看到完整回复内容,证明 Codex 已经回答了
87```
88
89## 修复前影响
90
91High —— 虽然 Codex 能正常回答,但调用方(conductor / AI caller)无法通过 session 状态 API 得知 turn 已完成,导致:
92
93- 轮询永远得不到 completed 信号
94- 回复内容只能通过绕路读 events.jsonl 获取,无法走正式 API
95- conductor 的任务编排无法基于 turn 完成事件触发后续动作
96
97## 修复建议
98
99已按方案 A 修复;没有引入超时、轮询或额外 completion 兜底。
100
101## 严重程度
102
103High —— 核心链路(发消息 → 得到完成状态)不通,调用方无法可靠地知道 Codex 什么时候回复完了。
104
105## 发现时间
106
1072026-03-24 by Claude
108
109## 备注
110
111- BUG-008 和本 bug 是同一条 transport 收尾缺陷在多 session 路径上的放大表现;见 `BUG-008` 当前状态说明
112- events.jsonl 里已确认的完整 agentMessage:「可以,我现在能稳定工作。」,对应 turn 019d1b95-b947-72c1-b6c2-02ee0a56202e
113- lastTurnStatus 可能的值:inProgress / completed / failed;问题是 completed 从未出现
114
115## Reopen 规则
116
117下面这些仍算 `BUG-010` 已修复范围,不 reopen:
118
119- `turn/completed` 的确发出了,只是恰好落在无换行尾包
120- recent events 中能看到该 turn 的 `app-server.turn.completed`
121- session 最终收口到 `lastTurnStatus: completed`
122
123下面这些应视为新的 bug:
124
125- transport / child 提前断流,recent events 出现 `app-server.transport.closed`
126- 同一 turn 没有任何合法 `app-server.turn.completed`
127- `codexd` 记录 `app-server.turn.completed.missing`
128- 事件 detail 标明 `failureClass = "transport_closed_before_turn_completed"`
129
130## 剩余风险
131
132未来如果再次出现 `stdout` 提前结束,且最终没有合法 `turn/completed`,现在会被 codexd 明确归类为新的 child / transport 故障,不再混入本 bug。