Desktop renderer can show raw {"detail":"Unsupported content type"} after patch-only turn and stream-state desync
Summary
Codex Desktop displayed a raw error pill:
{"detail":"Unsupported content type"}
after a successful patch-only turn. The turn itself completed and the patch operations succeeded. Local evidence points to a Desktop renderer thread-state synchronization failure, not a CLI failure or a model/backend sampling failure.
PII scrubbed: local usernames, account IDs, email addresses, exact home paths, private repo names, and private task identifiers are omitted or generalized.
Environment
- App: Codex Desktop
- Bundled Codex CLI:
0.142.0-alpha.1 - Shell/PATH Codex CLI:
0.141.0 - Platform: macOS arm64
- Originator in rollout metadata: Codex Desktop
What Happened
- A turn performed only file edits via
apply_patch. - The rollout recorded three successful patch calls.
- The rollout then recorded
task_completewithlast_agent_message: null. - The Desktop UI showed the patch summary card, followed by a raw error pill containing
{"detail":"Unsupported content type"}. - On the next interaction, Electron logs showed renderer state-sync failures for the same conversation:
Item not found in turn state itemId=...Failed to apply patches for conversationId=... errorMessage="[Immer] minified error nr: 18..."
Evidence
The persisted rollout shows the patch-only turn completed successfully:
response_item custom_tool_call apply_patch status=completed
patch_apply_end success=true
response_item custom_tool_call_output
...
event_msg task_complete last_agent_message=null
The Electron Desktop log then shows the renderer losing incremental turn state:
error [electron-message-handler] Item not found in turn state itemId=<tool-call-id>
warning [electron-message-handler] Failed to apply patches for conversationId=<conversation-id> errorMessage="[Immer] minified error nr: 18..."
The bundled renderer code path applies follower patches and only logs on failure:
handleThreadStreamStateChanged(...) {
...
let conversation = this.params.threadStore.getConversation(conversationId);
if (conversation) try {
let next = applyPatches(conversation, change.patches);
this.params.threadStore.setConversation(next);
...
} catch (error) {
logger.warning(`Failed to apply patches for`, ...)
}
}
Ruled Out
codex --helpworks with both PATH CLI and bundled Desktop CLI.codex doctorreports no content-type or app-server failure; only terminalTERM=dumbis flagged in this shell context.- The model sampling request around the later observed recurrence completed with HTTP 200 in core logs.
- Searching the OSS source and bundled app assets did not find
Unsupported content typeas a hardcoded Desktop string, which suggests it is a surfaced response body rather than frontend copy.
Expected Behavior
A successful patch-only turn should render as completed without a raw JSON error pill. If a renderer follower falls out of sync, Desktop should recover by requesting or broadcasting a full conversation snapshot instead of leaving the UI in a partial/error state.
Actual Behavior
The UI showed a raw backend-style JSON body as a transcript error after the successful patch summary. Subsequent logs show renderer follower patch application failures for missing turn items and an Immer patch failure.
Suggested Fix Direction
- On
thread-stream-state-changedpatch application failure, fall back to a full snapshot/resync for that conversation. - Avoid rendering raw JSON response bodies such as
{"detail":"..."}directly as transcript/system-error content when the error is renderer-local or bridge-local. - Consider a regression test for a patch-only turn where no final assistant message exists and a follower client receives incremental patches out of order or against stale turn state.
Repro Notes
I could not reproduce via CLI-only commands; the issue appears Desktop-specific and tied to multiple renderer/client stream synchronization. The local app process had multiple renderer processes plus the app-server sidecar active when the error occurred.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗