TypeScript SDK splits valid JSON events on U+2028 in Node 24

Open 💬 0 comments Opened Jul 16, 2026 by wrizvi

What issue are you seeing?

On Node 24, @openai/codex-sdk can fail while consuming thread.runStreamed() when a valid Codex JSON event contains a literal U+2028 or U+2029 character inside a string. The SDK's CodexExec.run() uses node:readline to frame the CLI's LF-delimited JSON output. Node 24 also treats U+2028 and U+2029 as line separators, so one valid JSON record is split into invalid fragments before the SDK calls JSON.parse.

This was observed with Node 24.9.0 and @openai/codex-sdk 0.140.0. Current main still uses readline.createInterface() in sdk/typescript/src/exec.ts, and the same minimal reproduction fails on Node 24.14.0.

What steps can reproduce the bug?

Run this with Node 24:

import readline from "node:readline";
import { Readable } from "node:stream";

const record = JSON.stringify({ text: "before\u2028middle\u2029after" });
const lines = [];

for await (const line of readline.createInterface({
  input: Readable.from([`${record}\n`]),
  crlfDelay: Infinity,
})) {
  lines.push(line);
}

console.log(lines.length);
console.log(lines.map((line) => {
  try {
    JSON.parse(line);
    return true;
  } catch {
    return false;
  }
}));

Node 24.9.0 prints:

3
[ false, false, false ]

The unsplit record parses successfully. U+2028/U+2029 are legal within JSON strings, while NDJSON records are delimited by LF (optionally preceded by CR), so human-text readline semantics are not suitable for this protocol.

What is the expected behavior?

The TypeScript SDK should split Codex CLI output only on LF, strip a preceding CR for CRLF, preserve literal U+2028/U+2029 inside records, and emit a final unterminated record at EOF.

Additional information

A focused patch and Node 24 regression test are ready on wrizvi/codex:codex/sdk-lf-only-jsonl-framing. The decoder uses the stream's UTF-8 StringDecoder through setEncoding("utf8"), so multibyte characters remain intact across chunk boundaries. The compact test also covers CRLF and a final record without a trailing LF.

Validation completed on the patch:

  • focused exec.test.ts: 8/8 on Node 20.20.2, Node 24.9.0, and Node 24.14.0
  • TypeScript SDK Prettier and ESLint
  • TypeScript SDK build
  • git diff --check

The full SDK integration suite additionally requires a locally built Codex CLI binary; that is left to repository CI.

View original on GitHub ↗