App Server explodes websocket frames with mandatory generated image data
When asking Codex App Server for turns, it embeds full generated images data with each turn for the current page. For a thread that contains just 1 turn with 7 generated images, that's a 27 megabyte websocket frame. If you have more images per turn, more turns with generated images, higher res images etc, this hits user RAM hard.
For locally running CAS, generated image bytes aren't needed at all - the CAS client already has access to the filesystem and can read savedPath directly. For remote clients, generated image bytes would be better off loaded separately from general turn data, or least there should be an option to do that.
As a workaround, I'm doing manual streaming and parsing of CAS's websocket/json data to drop the image bytes field on the fly and avoid hitting user's RAM, if anyone else is looking for a solution. This still wastes tens or hundreds megabytes of i/o traffic between your CAS client and CAS because you have to physically consume that websocket frame, but at least RAM gets saved.
3 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Linux/stdin-stdout reproduction: default
thread/resumeemits a 23 MB JSONL recordI reproduced the same generated-image frame amplification through the app-server's stdio transport,
where it becomes a connection/recovery failure rather than only a RAM/traffic problem.
Environment:
codex-cli 0.149.07.0.0-30-genericx86_64codex app-server --stdio(newline-delimited JSON-RPC)auth.jsonThe stored thread had exactly seven completed
image_generation_endrecords. Their serializedrollout records totaled 22,612,401 bytes; the corresponding paired large tool-output records totaled
22,608,258 bytes. No prompt, path, image bytes, or raw history was retained in the measurement.
Request sequence:
initialize, requestid=1initializedthread/resume, requestid=2, default parameters (excludeTurnsabsent)Measured stdout records:
| Record | Bytes |
|---|---:|
| initialize response (
id=1) | 214 ||
configWarning| 500 ||
remoteControl/status/changed| 207 ||
thread/status/changed| 149 || thread/resume response (
id=2) | 23,159,303 |The oversized record is a response envelope (
id=2, nomethod), not an item/turn notification.A client with a bounded 16 MiB line reader cannot parse the envelope or resolve the pending resume
request. Discarding the record is not recovery because this is the only required response.
The existing experimental API is an effective control. With
initialize.capabilities.experimentalApi=trueandthread/resume(excludeTurns=true), the exact samethread returned a 5,104-byte
id=2response (99.978% smaller). It retained the matching thread id,status={"type":"idle"}, preview and runtime/config metadata, and returnedturns=[]. Without theinitialize capability, 0.149.0 correctly returned
-32600:thread/resume.excludeTurns requires experimentalApi capability.This is consistent with the current source comment that
thread/resumecan include large MCP andimage-generation payloads, while response redaction is limited to the two ChatGPT mobile remote
client names:
https://github.com/openai/codex/blob/main/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs
Expected behavior:
frame.
payloads, and clients should be able to negotiate it deterministically.
Related current report: #39148 shows
read_threadreturning multi-megabyteimageGeneration.resultdespite
includeOutputs:falseandmaxOutputCharsPerItem.We mitigated the client side by negotiating
experimentalApi, usingexcludeTurns:truefor storednative resumes, and treating any lost oversized JSONL record as terminal transport corruption rather
than continuing with unresolved request correlation
(metadata-only resume and typed failure).
This avoids the crash, but it does not remove the upstream unbounded default response.
Minimal response-size scanner
Run only against a disposable
CODEX_HOMEcontaining an image-heavy test thread. It does not start amodel turn and never retains the response payload; it reports envelope id/method and byte length.
Expected on the measured test thread:
I also opened #40362 for the distinct bounded-stdio/RPC-recovery failure mode: the mandatory
thread/resumeresponse exceeds a line-framed client limit, so response correlation is lost and reconnect repeats the same frame. The measurements and scanner above are mirrored there.