Responses WebSocket falls back when compacted replacement_history contains large inline images
What version of Codex CLI is running?
codex-cli 0.133.0.
The original affected session metadata includes codex-cli 0.130.0, but the WebSocket fallback was reproduced from a copied session using the current Codex CLI/App around 0.133.0.
What subscription do you have?
ChatGPT Pro 20x.
Which model were you using?
gpt-5.5 with reasoning_effort=xhigh.
What platform is your computer?
macOS arm64.
What terminal emulator and version are you using (if applicable)?
iTerm2 3.6.10.
The affected thread was resumed through Codex CLI/App flows. The reproduction used copied CODEX_HOME directories and codex resume against the same session JSONL payload. No terminal multiplexer was intentionally used for the reproduction session; separate monitoring/caffeinate processes may have been run in other terminals.
Codex doctor report
Redacted summary from codex doctor --json:
- overallStatus:
ok - codexVersion:
0.133.0 - install method:
brew - runtime platform:
macos-aarch64 - installation:
installation looks consistent - config:
config loaded - model:
gpt-5.5 - model provider:
openai - auth:
auth is configured - auth mode:
chatgpt - provider reachability:
active provider endpoints are reachable over HTTP - WebSocket reachability:
Responses WebSocket handshake succeeded - WebSocket handshake result:
HTTP 101 Switching Protocols - WebSocket wire API:
responses - terminal:
iTerm2 - terminal version:
3.6.10 - terminal size:
211x98 - state paths:
state paths and databases are inspectable - active rollout files:
110 files, 1371653678 total bytes, 12469578 average bytes
I am not pasting the raw doctor JSON because it contains local usernames, paths, project names, and state locations.
What issue are you seeing?
A long-running Codex thread consistently falls back from Responses WebSocket to HTTPS when resumed.
This may share a root cause with #24388, because both involve large input_image payloads retained inside compacted replacement_history. The observed failure mode here is different: instead of remote compaction failing with context_length_exceeded, the Responses WebSocket stream closes before response.completed, retries several times, and then falls back to HTTP.
I was able to reproduce the fallback using a copied CODEX_HOME, and then eliminate the fallback by removing only inline image payloads from compacted replacement_history.
Observed WebSocket failure pattern:
stream disconnected - retrying sampling request (1/5 ...)
stream disconnected - retrying sampling request (2/5 ...)
stream disconnected - retrying sampling request (3/5 ...)
stream disconnected - retrying sampling request (4/5 ...)
stream disconnected - retrying sampling request (5/5 ...)
falling back to HTTP
The HTTP fallback then proceeds, but the affected thread becomes prone to long idle periods.
What steps can reproduce the bug?
I cannot attach the raw session JSONL because it contains local paths, prompts, tool output, and image payloads. The structural reproduction I used was:
- Start from a long-running Codex thread with multiple compactions and historical image inputs.
- Confirm that compacted
replacement_historycontains retainedinput_imagepayloads. - Copy the affected session JSONL into a clean temporary
CODEX_HOME. - Resume the same thread from the copied session.
- Observe that WebSocket warmup succeeds, but the actual resumed turn payload fails over WebSocket, retries 5 times, and falls back to HTTP.
- Create another copy of the same session JSONL.
- Replace only
input_imageitems insidetype: "compacted"records with small text placeholders. - Resume the same thread from that sanitized copy.
- Observe that
responses_websocketcompletes normally, with no HTTP fallback and nostream disconnectedretries.
Control reproduction with copied session JSONL:
- WebSocket warmup succeeded.
- The actual resumed turn payload failed over WebSocket.
stream disconnectedoccurred 5 times.- Codex fell back to HTTP.
This suggests the issue is reproducible from the session/history payload itself, not from the original live ~/.codex state.
Compaction-only sanitized reproduction:
- I replaced only
input_imageitems insidetype: "compacted"records with text placeholders. - Non-compacted history was left mostly unchanged.
Before sanitization:
- Session JSONL size: ~703 MB
- Largest compacted JSONL line: ~17.4 MB
- Total
input_imageentries: 1789 input_imageentries inside compacted records: 1277input_imageentries outside compacted records: 512- Latest compacted
replacement_history: ~17.39 MB - Inline image data in that replacement history: ~17.17 MB
- Image data share of replacement history: ~98.75%
After compaction-only sanitization:
- Session JSONL size: ~195 MB
- Largest compacted JSONL line: ~241 KB
input_imageentries inside compacted records: 0input_imageentries outside compacted records: 509
Result:
responses_websocketcompleted normally.responses_http: 0stream disconnected: 0- fallback: 0
- turn completed successfully.
This suggests the failure is specifically tied to inline images retained in compacted replacement_history, not simply to any historical input_image item anywhere in the session JSONL.
I also tested a full-image-sanitized copy, where all input_image items were replaced with text placeholders. That also completed normally over WebSocket.
What is the expected behavior?
Either:
- compacted
replacement_historyshould not retain large raw inline image payloads unnecessarily, - the Responses WebSocket transport should handle this payload shape reliably,
- Codex should surface a more specific close reason, such as payload size / unsupported multimodal replay / request shape issue, or
- Codex should automatically recover from this history shape without requiring manual session JSONL surgery.
A session should not repeatedly fail Responses WebSocket and silently fall back to HTTP because compacted history retained large inline image payloads.
Additional information
The thread did not always fail WebSocket. It worked over WebSocket for several days before the first hard fallback.
The first hard fallback appeared after a compaction where replacement_history had grown to ~17.38 MB and contained 42 inline images.
In the latest compaction:
- Messages in replacement history: 382
- Messages containing images: 18
input_imagecount in replacement history: 42- Image bytes inside image-containing messages: ~99.88% of those messages
The images appear to be retained as historical user message content inside compacted replacement history.
Privacy note: I am not attaching raw rollout/session files because they contain private prompts, tool output, local paths, and inline image data. I can provide further redacted structural data if useful.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗