What happened
The Codex app-server protocol accepts a newline-delimited JSON message of any size. It stores fragments until a newline arrives, joins them into one string, and then parses the full JSON value. The parser has no byte limit before these allocations.
The current count limit does not address this case. MAX_BUFFERED_RAW_MESSAGES = 32 limits decoded queue entries, not the size of one input message.
This install has received Codex events containing diff strings near 49 million characters. A single valid provider notification can therefore require a large line string, a parsed object, and later runtime event objects at the same time.
Diagnosis
makeCodexAppServerPatchedProtocol reads text chunks from the child process and stores unfinished line fragments in remainder. It tracks no byte count. When it finds a newline, it runs remainder.join(""), then decodeWireMessage parses the result through Schema.fromJsonString.
The protocol applies its 32-message sliding queue only after JSON decoding and routing. The bound cannot stop one oversized line from exhausting the heap during line assembly or parsing.
Issue #5389 fixed quadratic input scanning. The current loop scans each chunk once, so that fix is present. It did not add a per-message byte limit.
Expected behavior: the protocol should enforce a documented byte ceiling while it receives line fragments. If a message exceeds the ceiling, T3 should stop or fail that provider session with a clear error instead of joining and parsing the full value. The check must run before remainder.join("").
Steps to reproduce
Synthetic reproduction:
- Create a fake Codex app-server standard input stream.
- Send one newline-delimited JSON notification whose
params contains a generated string large enough to approach the V8 heap limit.
- Split the message across many input chunks so the protocol stores fragments in
remainder.
- End the line with a newline.
- Observe that the protocol joins and parses the full message without checking its byte size.
A focused regression test can add an injectable low message limit, send fragments that cross it, and assert that parsing never runs and the session receives a typed transport error.
Version
0.0.43-nightly.20260920.2005, commit 7445aa733ada.
Environment
- T3 Code desktop app with local backend
- Darwin 25.6.0, arm64
- Node.js 26.8.2
- Codex provider
Evidence
Current count bound:
MAX_BUFFERED_RAW_MESSAGES = 32
Unbounded line state:
const remainder: Array<string> = []
Line assembly:
remainder.push(chunk.slice(start, newline))
lines.push(remainder.join(""))
Decode order:
assembled line -> decodeWireMessage(line) -> routeMessage(decoded)
Largest decoded file-change diff seen on this install:
about 49 million characters
No home paths, thread IDs, turn IDs, project names, commands, diff text, or credentials are included.
Related issues
No open issue or pull request found in the upstream search covers the missing pre-parse byte limit.
Fix applied or workaround
No source patch was made. Avoiding very large provider messages reduces risk, but T3 has no local setting for a protocol message limit.
Filed by
Codex, GPT-5, via t3 triage
What happened
The Codex app-server protocol accepts a newline-delimited JSON message of any size. It stores fragments until a newline arrives, joins them into one string, and then parses the full JSON value. The parser has no byte limit before these allocations.
The current count limit does not address this case.
MAX_BUFFERED_RAW_MESSAGES = 32limits decoded queue entries, not the size of one input message.This install has received Codex events containing diff strings near 49 million characters. A single valid provider notification can therefore require a large line string, a parsed object, and later runtime event objects at the same time.
Diagnosis
makeCodexAppServerPatchedProtocolreads text chunks from the child process and stores unfinished line fragments inremainder. It tracks no byte count. When it finds a newline, it runsremainder.join(""), thendecodeWireMessageparses the result throughSchema.fromJsonString.The protocol applies its 32-message sliding queue only after JSON decoding and routing. The bound cannot stop one oversized line from exhausting the heap during line assembly or parsing.
Issue #5389 fixed quadratic input scanning. The current loop scans each chunk once, so that fix is present. It did not add a per-message byte limit.
Expected behavior: the protocol should enforce a documented byte ceiling while it receives line fragments. If a message exceeds the ceiling, T3 should stop or fail that provider session with a clear error instead of joining and parsing the full value. The check must run before
remainder.join("").Steps to reproduce
Synthetic reproduction:
paramscontains a generated string large enough to approach the V8 heap limit.remainder.A focused regression test can add an injectable low message limit, send fragments that cross it, and assert that parsing never runs and the session receives a typed transport error.
Version
0.0.43-nightly.20260920.2005, commit7445aa733ada.Environment
Evidence
Related issues
thread/resumeresponse. This report applies to any oversized app-server message.No open issue or pull request found in the upstream search covers the missing pre-parse byte limit.
Fix applied or workaround
No source patch was made. Avoiding very large provider messages reduces risk, but T3 has no local setting for a protocol message limit.
Filed by
Codex, GPT-5, via
t3 triage