[Bug] Desktop multi-session send never completes (outgoing_message.rs:563 missing turn/completed event)
Codex Desktop — multi-session send 永不完成 Bug 报告
Reporter: user (Codex Desktop 26.623.8305.0 / Codex CLI 0.142.4 on Windows 11)
Repro platform: Windows 11 Pro 10.0.26200
Repro date: 2026-06-30
Severity: High — 单会话能工作,多会话/新会话完全卡死
---
TL;DR
Codex Desktop 在新建(第二个及之后)会话后,用户点发送,模型实际回复正常,但 Desktop UI 永远停留在 "Working..." 状态,前端 watchdog 数秒后兜底重置 loading,UI 表现即"消息凭空消失 + loading 自动消失"。根因是 Rust app-server 端 codex_app_server::outgoing_message 层在 turn 状态机完成后未向 Desktop stdio 发 turn/completed 终态事件,导致 Renderer 永远收不到 stream end,UI 卡在 loading。
---
Reproduction Steps
- 启动 Codex Desktop(AppX 26.623.8305.0)
- 等待 onboarding 完成
- 在会话 A 中发送一条消息 → 正常收到 AI 回复(A 工作正常)
- 不关闭 A,通过 Ctrl+N 或 sidebar "New chat" 创建会话 B
- 在会话 B 输入任意文字,点发送
- 观察:
- 按钮进入 loading 状态 ✅
- 输入框文字已清空 ✅
- B 对话流未出现 user message ❌
- A 对话流无变化(消息未串会话)
- 数秒后 loading 自动消失(前端 watchdog 兜底)
- 整段等待期间无 AI 回复,但模型其实已经回完
- 刷新 Codex Desktop / 退出重启 → 恢复(仅本次新建会话能工作,再次新建第二个会话又卡)
---
Expected vs Actual
| | Expected | Actual |
|---|---|---|
| B 点发送后 | user message 立即出现在 B 流 | 流始终空 |
| AI 回复 | 在合理时间内到达 B 流 | 永远不到达(但模型侧已生成) |
| loading 熄灭 | 收到完整回复后 | 数秒后被前端 watchdog 强制重置 |
| 多会话隔离 | A/B 互不影响 | B 完全无法发送,必须刷新 |
---
Root Cause(基于 logs_2.sqlite 真实证据,非症状匹配)
Module: codex_app_server::outgoing_message
File: app-server/src/outgoing_message.rs:563
Failure mode: turn 状态机完成事件未被序列化发送至 Desktop stdio
证据 1 — outgoing_message 单点 hot line
SELECT DISTINCT file, line, COUNT(*) FROM logs
WHERE module_path='codex_app_server::outgoing_message'
GROUP BY file, line ORDER BY 3 DESC LIMIT 5;
app-server\src\outgoing_message.rs | 563 | 7520
outgoing_message.rs:563 在所有 7520 条该模块日志中被唯一命中 → 序列化路径单点。
证据 2 — 全部 TRACE,零 ERROR/WARN
SELECT level, COUNT(*) FROM logs
WHERE module_path='codex_app_server::outgoing_message'
GROUP BY level;
TRACE | 7520
ERROR | 0
WARN | 0
→ 没有"完成事件发送失败"错误,因为根本没尝试发完成事件。
证据 3 — turn 内核侧正常完成
SELECT MAX(ts), MIN(ts), COUNT(*) FROM logs
WHERE module_path='codex_core::session::turn';
1782757488 | 1782219802 | 892
core/src/session/turn.rs:308 / 1949(共 463 + 425 + 4 = 892 条)按 thread_id 正常分布:
019f0962-... | 76
019ef4d5-... | 67
019f0a15-... | 65
...(每个 thread 33-76 条)
→ 内核侧 turn 状态机走完完整生命周期。问题不在 turn 状态机本身。
证据 4 — outgoing_message 缺 thread_id 维度
SELECT COUNT(*) FROM logs
WHERE module_path='codex_app_server::outgoing_message'
AND thread_id IS NOT NULL;
0
→ outgoing_message 层向桌面发事件时不带 thread_id,意味着事件无法被桌面正确路由到目标会话的 stream 收尾。
证据 5 — session_index 当天零更新
cat ~/.codex/session_index.jsonl | jq -r '.updated_at' | sort -u | tail -5
全部 thread 的 updated_at 停在 2026-06-29 —— 2026-06-30 当天 Desktop 一次都没成功写入新 thread,直接坐实 outgoing_message 缺完成事件。
证据 6 — Desktop global state unread 列表为空
.codex-global-state.json 中:
"unread-thread-ids-by-host-v1": {"local": []}
按设计,卡住的会话会进 unread 等用户重试。空列表说明桌面根本没意识到有未完成的回复 —— 终态事件缺失的直接表征。
---
Why "Refresh Fixes It"
刷新杀掉 Renderer 进程 → loading 状态被进程销毁清掉 → 下次打开是新进程 + 干净状态。但这是伪恢复:
- 第一个会话(重启后建的第一个新会话)能用,因为 Renderer 走"新建会话"路径
- 第二个会话又卡,因为 Renderer 走了"已有会话切到新会话"路径,触发 outgoing_message 缺完成事件的 bug
---
Suggested Fix(对上游)
在 app-server/src/outgoing_message.rs:563 之后,确保 turn 状态机走到 Completed 时向 Desktop stdio 发 turn/completed 事件(带 thread_id + turn_id),让 Renderer 能正确收尾 stream end、熄灭 loading。
参考已有中间事件序列化路径(line 563)补一个终态分支即可。
---
Workaround(用户侧)
- 只用第一个会话(每次启动后只开一个新会话,不切不新建)
- 用 CLI/TUI 替代 Desktop:
````
D:\codex-cli\codex.exe
TUI 不走 app_server 层,绕过该 bug。
- 每次新会话前先关闭 Codex Desktop 再重启(仅当前会话能用)
---
Environment
Codex Desktop: 26.623.8305.0 (AppX, winget: No newer package versions available)
Codex CLI: 0.142.4 (latest, 已 dismiss 0.142.3 升级提示)
OS: Windows 11 Pro 10.0.26200
shell: bash (Git Bash)
Process count: 11 Codex.exe 同时在跑(潜在资源/句柄占用问题)
tmp residue: ~/.codex/tmp/arg0/codex-arg0r2u6kQ/.lock + apply_patch.bat 残留
app-server-smoke-err.log: "WARNING: failed to clean up stale arg0 temp dirs: 拒绝访问"
---
Closing Notes
- 该 bug 在用户机器上已稳定复现,6 月 30 日全天无任何 Desktop 会话成功完成
- 用户已在
~/.codex/.codex-global-state.json的 prompt-history 中留下相关查询痕迹("Console / Network 面板这些怎么打开?"),说明用户本人也在调查 - v8 多会话排障 Agent 已验证症状模式匹配(Dispatcher global isSending 假设)不成立,真实根因在 Rust 端 outgoing_message 层
2 Comments
English translation:
Codex Desktop — Multi-Session Send Never Completes Bug Report
Reporter: user (Codex Desktop 26.623.8305.0 / Codex CLI 0.142.4 on Windows 11)
Repro platform: Windows 11 Pro 10.0.26200
Repro date: 2026-06-30
Severity: High — single sessions work, but multi-session/new-session usage completely hangs
---
TL;DR
After Codex Desktop creates a new session (the second and subsequent sessions), when the user clicks send, the model actually responds normally, but the Desktop UI stays in the "Working..." state forever. After a few seconds, the frontend watchdog falls back to resetting loading, and the UI appears as "the message disappears into thin air + loading disappears automatically." The root cause is that after the turn state machine completes, the Rust app-server layer
codex_app_server::outgoing_messagedoes not send the terminalturn/completedevent to Desktop stdio, causing the Renderer to never receive stream end and the UI to remain stuck in loading.---
Reproduction Steps
---
Expected vs Actual
| | Expected | Actual |
|---|---|---|
| After clicking send in B | user message immediately appears in the B stream | stream remains empty |
| AI reply | arrives in the B stream within a reasonable time | never arrives (but the model side has generated it) |
| loading stops | after receiving the complete reply | forcibly reset by the frontend watchdog after a few seconds |
| multi-session isolation | A/B do not affect each other | B cannot send at all and must be refreshed |
---
Root Cause (based on real evidence from logs_2.sqlite, not symptom matching)
Module:
codex_app_server::outgoing_messageFile:
app-server/src/outgoing_message.rs:563Failure mode: the turn state machine completion event is not serialized and sent to Desktop stdio
Evidence 1 — outgoing_message single hot line
outgoing_message.rs:563is the only hit among all 7520 logs for this module → single point on the serialization path.Evidence 2 — all TRACE, zero ERROR/WARN
→ There is no "failed to send completion event" error, because no attempt was made to send the completion event at all.
Evidence 3 — turn completes normally on the core side
core/src/session/turn.rs:308 / 1949(463 + 425 + 4 = 892 entries total) are distributed normally by thread_id:→ The turn state machine on the core side goes through its full lifecycle. The problem is not in the turn state machine itself.
Evidence 4 — outgoing_message lacks the thread_id dimension
→ When the
outgoing_messagelayer sends events to Desktop, it does not include thread_id, which means Desktop cannot correctly route events to close the target session's stream.Evidence 5 — session_index had zero updates that day
All threads'
updated_atvalues are stuck on 2026-06-29 — on 2026-06-30, Desktop did not successfully write any new thread even once, directly confirming that outgoing_message is missing the completion event.Evidence 6 — Desktop global state unread list is empty
In
.codex-global-state.json:By design, stuck sessions should enter unread and wait for the user to retry. The empty list shows that Desktop is not aware there is an unfinished reply at all — a direct sign that the terminal event is missing.
---
Why "Refresh Fixes It"
Refreshing kills the Renderer process → the loading state is destroyed and cleared with the process → the next open uses a new process + clean state. But this is a false recovery:
---
Suggested Fix (for upstream)
After
app-server/src/outgoing_message.rs:563, ensure that when the turn state machine reachesCompleted, aturn/completedevent (withthread_id+turn_id) is sent to Desktop stdio, so the Renderer can correctly close stream end and stop loading.Use the existing intermediate-event serialization path (line 563) as reference and add a terminal-state branch.
---
Workaround (user side)
``
``D:\codex-cli\codex.exe
TUI does not go through the
app_serverlayer, bypassing this bug.---
Environment
---
Closing Notes
~/.codex/.codex-global-state.json("How do I open the Console / Network panels?"), indicating the user was also investigating<!-- codex-issue-translator -->
Amendment after fresh diagnostic (2026-06-30 04:05 UTC+8)
After running additional traces against the active 4 sessions created during Desktop restart tests with
multi_agent = true+followUpQueueMode = "steer", I have corrected evidence that changes the root-cause attribution.What I observed
Result:
What this means
ALL sessions — including the stuck B/C sessions — flow through
codex_client::transportand never touchcodex_app_server::*(except a single thread_lifecycle event).The earlier attribution to
app-server/src/outgoing_message.rs:563was wrong. Theoutgoing_message.rs:563TRACE lines (7878 of them) are unrelated activity (ambient suggestions, remote control sync, etc.) — none of them carry a thread_id and none of them correspond to the user's send clicks.Real picture
| Stage | Module | Status |
|---|---|---|
| E1 UI click | (Electron renderer) | ✅ |
| E2 dispatch |
codex_core::session::turn(line 1949) | ✅ || E3 transport |
codex_client::transport→hyper→ HTTPS to relay | ✅ || E4 stream events |
codex_core::stream_events_utils(line 524, 4-46 events) | ✅ — model did reply || E5 turn close |
codex_core::session::turn(line 308, TRACE) | ✅ — turn state machine ended || E6 notify Renderer | MISSING | ❌ |
For session B/C, turn lifecycle ends cleanly but no completion signal reaches the Electron Renderer, so loading spins forever until the frontend watchdog resets it.
Revised suggested fix area
Instead of patching
app-server/src/outgoing_message.rs:563(the wrong layer), the bug is likely in the bridge betweencodex_core::session::turncompletion and the Renderer-facing IPC stream.Candidates to investigate upstream:
codex_core::session::turn→ completion event fan-outcodex_client::transportstream endReproduction reliability
Confirmed reproducible after every Desktop cold start: the very first send in the first session works; creating any second session (
Ctrl+N) and sending in it always hangs.Sorry for the earlier wrong attribution
The earlier root-cause attribution to
outgoing_message.rs:563was based on a static baseline that included unrelated CLI/sync activity. This amendment supersedes it.