Skip to content

Codex app-server input has no per-message byte limit #12884

Description

@alexfertel

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:

  1. Create a fake Codex app-server standard input stream.
  2. Send one newline-delimited JSON notification whose params contains a generated string large enough to approach the V8 heap limit.
  3. Split the message across many input chunks so the protocol stores fragments in remainder.
  4. End the line with a newline.
  5. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions