Repository navigation
[macOS][Codex Desktop] Chrome rollout tracker loops at >160% CPU when session JSONL exceeds V8 max string length #38611
Description
Codex app version
26.803.61601 (build 6396)
Platform
macOS 27.0 beta (26A5388g), Apple Silicon
Summary
The bundled Chrome-control browser-client.mjs rollout tracker enters a permanent high-CPU/high-allocation retry loop when it attaches to a long-running Codex Desktop task whose rollout JSONL exceeds V8's maximum JavaScript string length.
The observed task remained active and usable, but the Chrome-control Node helper sustained approximately 160-167% CPU until terminated.
Reproduction conditions
- Keep a Codex Desktop task active long enough that its
~/.codex/sessions/.../rollout-*.jsonlgrows beyond Node/V8's maximum string length. - Initialize the bundled Chrome-control browser runtime in that task.
- The rollout tracker discovers the current session file with
fileOffset = 0. - It reads the entire rollout in 64 KiB chunks, concatenates those buffers, and calls
.toString("utf8"). - Conversion fails before the returned offset can be assigned.
- The tracker catches the failure without disabling tracking or advancing the offset, then polls again after 250 ms and repeats from byte zero.
Sanitized measurements from the affected task
- Rollout JSONL size:
1,148,831,430bytes - Bundled Node
buffer.constants.MAX_STRING_LENGTH:536,870,888bytes - JSONL records:
24,675 compactedrecords: 35 records totaling828,221,910bytes- Largest individual record:
33,878,758bytes - Hot helper CPU: approximately
160-167% - Current helper RSS during sampling: approximately
3.8 GiB - Physical-footprint peak:
12.1 GiB - Native stack sample was dominated by asynchronous filesystem reads, ArrayBuffer allocation, and V8 garbage collection.
- The helper held the same rollout file open twice during the loop.
- No macOS thermal or performance warning was reported.
No private task history or browser data is attached.
Relevant installed-runtime behavior
The bundled tracker:
- polls every 250 ms while a rollout is active;
- starts newly discovered sessions at offset zero;
- reads all remaining bytes into an array of 64 KiB buffers;
- concatenates all buffers and converts them into one UTF-8 string;
- updates
fileOffsetonly after that operation returns successfully; - catches processing errors and leaves polling active.
Because the observed rollout is more than twice MAX_STRING_LENGTH, the initial conversion cannot succeed. The saved offset therefore remains zero and each poll repeats the full read/allocation attempt.
Expected behavior
Rollout tracking should remain bounded regardless of task-history size. For example:
- tail only a bounded recent portion when attaching to an existing rollout;
- stream JSONL incrementally rather than materializing it as one string;
- cap each read/parse batch;
- advance or checkpoint the offset safely;
- stop retrying or disable tracking after an unrecoverable size error;
- surface a useful diagnostic instead of swallowing the failure.
Actual behavior
One Chrome-control helper continuously rereads and reallocates the oversized rollout, consuming more than one CPU core and several GiB of memory even after browser operations have finished.
Related issues
- Codex Desktop becomes unusable on long active threads due to app-server/renderer memory and TRACE log churn #21134 — long active desktop threads causing CPU/memory degradation
- Desktop threads can be poisoned by inline base64 tool images, leading to
{"detail":"Bad Request"}on resume and likely inflated token usage #18629 — inline browser images inflating/poisoning desktop thread histories
Those reports describe related history growth and long-thread degradation, but not this specific rollout-tracker string-limit retry loop.
Activity
perhapseb commentedon Aug 14, 2026
Private in-app diagnostics have now been submitted from the affected task with both current session logs and browser tabs/browser logs included.
Feedback ID:
019fdd30-fe5e-75d0-b47e-2d6178bde306This identifier also corresponds to the affected Codex task referenced in the report, which should help correlate the uploaded diagnostics with the oversized rollout and Chrome-helper reproduction.
karan-jani commentedon Sep 3, 2026
Additional observation on 2026-09-02, still present in ChatGPT/Codex desktop 26.901.20858 (build 7658).
Environment:
0.153.0-alpha.5v24.19.026.901.20858The Mac became unusually hot at the time the CUA trusted worker started. The worker sustained approximately 60–85% of one CPU core across repeated samples and had accumulated more than an hour of CPU time over roughly two hours. Its sampled physical footprint was 833.9 MB, later rose to about 1.6 GB, and the recorded peak was 5.3 GB.
The process was
cua_node/bin/node .../trusted-worker.js. It held a large Codex rollout JSONL open, including two descriptors in one observation. A nativesamplecaptured asynchronous filesystem reads (node::fs::Read,pread), UTF-8 string conversion (StringSlice,StringBytes::Encode,simdutf), and V8 allocation/garbage collection.Measured file/runtime limits:
buffer.constants.MAX_STRING_LENGTH = 536870888(a string-length limit, not generally a UTF-8 byte limit).Inspection of the installed
@oai/browser-desktop/scripts/browser-service.mjsshows the same mechanism described here: new sessions start atfileOffset: 0;JYreads all unread data into 64 KiB buffers and callsBuffer.concat(...).toString("utf8");Xm.processSessionRolloutonly updates the offset after success; other processing errors are caught while polling remains active, with a 250 ms polling delay. That file cannot fit into a single string in this runtime.The exact runtime exception was not captured before termination, so the string-limit retry mechanism is a strongly supported diagnosis, not a fresh controlled reproduction. The stack sample and installed code match this issue closely.
Terminating only the affected trusted worker removed its persistent CPU load, and the user then confirmed that the Mac was cooling. GPU utilization remained around 30%, so we are not attributing all system activity to this helper.
pmset -g thermreported no recorded thermal or performance warning.Please prioritize bounded/streaming rollout parsing and handling for unrecoverable conversion errors so the watcher cannot repeatedly reread the same oversized history. No private conversation contents, browser data, or raw session logs are included in this report.
AdamArroyo92 commentedon Sep 6, 2026
Additional Windows reproduction on September 5, 2026, with a much larger rollout and the same unbounded reader still present in a newer installed runtime.
Environment
Observed impact
A long-lived task's rollout JSONL is 18,575,189,258 bytes (file metadata measured without loading the transcript).
During an earlier explicitly authorized read-only browser test in that task, opening a hosted game-server control panel and navigating to File Manager coincided with:
Fresh-task comparison
A new task on the same machine, with a ~1.6 MB rollout at inspection, performed read-only browser discovery and navigation through the same control panel: Console, File Manager, oxide/plugins directory, Server Metrics, Players, then Console.
A bounded watchdog recorded 112 samples over ~104 seconds including setup/cleanup:
The test tab was closed and the identified test workers terminated. No hosted files, settings, commands, or server state were changed by the test.
Static code evidence in this build
In browser-service.mjs, around line 4205, the minified Xm tracker and JY reader still:
For this transcript, the retained chunks plus a concatenated buffer alone total approximately 35,429 MiB, close to the observed 35,806 MiB private allocation, before text decoding/parsing and other overhead. This is strong evidence for the mechanism described in this issue; we did not take a heap dump or rerun the dangerous large-task case.
Trigger nuance: Gm.getInfo enables tracking for extension/cdp backends, not directly for iab. The CUA discovery used in these tests lists an available Chrome extension as well as the in-app browser; getUserTabs routes through sendSessionRequest and its tracker. Therefore do not attribute the full read exclusively to the hosted page or iab navigation. Please investigate discovery/extension inventory as a trigger even when the intended working tab is iab.
Suggested fix remains bounded streaming/tailing and controlled handling of unrecoverable size errors; also ensure reset/disposal cancels an in-progress read. No runtime patch or hidden environment-variable workaround was applied.
Private transcript contents, server address/identifiers, credentials, and screenshots are deliberately omitted. Posted by Codex at the affected user's explicit request.
jsegeren commentedon Sep 7, 2026
Additional macOS measurements and proposed bounded reader fix (September 6, 2026)
Posted by Codex on behalf of the affected user, with explicit authorization. This adds a larger scale case and a tested reference implementation to this existing issue. Current desktop version: 26.901.51231 (build 8109).
Summary
The Codex unified browser helper can allocate tens of gigabytes while detecting turn-completion events in a large JSONL rollout. The installed tracker starts with fileOffset=0 and pendingFragment="". Its reader collects every available 64 KiB chunk, Buffer.concat() copies them, toString("utf8") creates a large string, and split("\n") materializes records. Incomplete giant records also grow pendingFragment without a bound. Failed reads/parses can leave the committed offset unchanged, allowing repeat scans. File-change notifications additionally queue a separate processing promise for every notification.
Observed impact
On an Apple M5 Max MacBook Pro with 128 GB RAM and macOS 26.5, a trusted browser worker held two read handles on a 23,330,114,904-byte rollout. A process sample recorded peak physical footprint 48.8 GiB, file reads and garbage collection. System disk traffic sampled around 282–358 MB/s, with approximately 21 GiB swap occupied. The worker later restarted and grew again. No user transcript is included in this report.
The user also experienced system stutters and AirPods audio becoming silent while shown as connected; reconnecting restored playback. Bluetooth retry counters rose during one incident. A causal relationship between this reader and Bluetooth failure has NOT been established. The reader's excessive memory use is independently reproducible from its implementation.
Affected code
Observed in @oai/browser-desktop/scripts/browser-service.mjs used by unified-computer-use 26.901.41123 and still present with 26.901.51231. Installed application-bundle SHA-256: 3032666de451e423db76b5f9b0456e370d4f57416bbf53d0c746be636359ba02. Relevant minified reader: JY; tracker: Xm. Function names may change by build.
Proposed fix
Retain both native turn-ended events and transcript fallback. Replace whole-file accumulation with a resumable streaming JSONL reader that selects only lifecycle fields and active turn IDs. Bound bytes per scan, preserve parser/cursor state across partial records, and coalesce pending scan requests. Handle truncation, inode replacement, malformed lines and excessive record nesting explicitly.
The reference implementation below uses 64 KiB buffers, an 8 MiB scan budget, stream-json with value packing disabled, and a 128-container record-depth limit. It does not cap agent concurrency or change CPU priorities. Huge strings and keys are consumed without retaining their contents. No completion ID is emitted until the containing line validates and ends with a newline. Historical IDs not currently tracked are not retained.
Reproduction and validation
Use synthetic data only. benchmark.cjs streams a configurable large single JSONL record to a temporary file, places a lifecycle turn ID after the large string, runs the reader and reports peak RSS. It deletes the fixture afterward. A 256 MiB single-record run completed in 33 bounded scans, 483 ms, peak RSS 71 MiB, under --max-old-space-size=128. Seven tests passed covering record semantics, giant strings and keys, byte boundaries/Unicode, malformed input, partial append, truncation/replacement, native/fallback cleanup, and notification coalescing. The tests that extract the installed tracker need build.py to prepare the runtime first.
This is a proposed local implementation, not an official patch. Review the 8 MiB scheduling budget, 128-level depth policy, full-history disk cost, and lifecycle/cleanup integration upstream. A maintained streaming parser already used within the product may be preferable to adding the dependency chosen for this local demonstration.
Local installation scope
A private local copy of the service is loaded through the unsigned plugin launcher, guarded against an upstream hash change. The signed application remains unchanged. Existing running helpers were not interrupted. End-to-end browser use with a freshly loaded helper and long-term audio stability remain to be observed.
Reference implementation and synthetic tests
These expandable sections contain the proposed reader, tests, benchmark, and a build-specific local integration script. No private conversation contents, account identifiers, credentials, device serials, or full proprietary service bundle are included. The integration script is provided to make the seven tests reproducible against the stated installed build; it creates a local copy and does not install the launcher override. This is a review candidate, not an upstream-reviewed production fix.
Save these five files in one new directory, run
npm install --ignore-scripts, thenpython3 build.pyon the stated macOS build. Runnode --test test.cjsandnode --max-old-space-size=128 benchmark.cjs 256using the bundled Node runtime or a compatible Node version. The first five reader tests and benchmark use only synthetic fixtures; the last two tests extract the tracker from the locally installed service.package.json
{ "dependencies": { "stream-json": "1.9.1" } }reader.cjs
test.cjs
benchmark.cjs
build.py
kernelpanic888 commentedon Sep 14, 2026
Issue: #38611.
This reviews the streaming-reader proposal by jsegeren. It is NOT a claim to have patched or reproduced the currently installed browser helper. The original high-memory retry mechanism and the proposed streaming design were already documented by others.
New small counterexample for the proposed reader
The proposal's test oracle uses JSON.parse to reject malformed records. With its pinned stream-json 1.9.1 parser, a non-breaking space before a colon is accepted as whitespace and a completion is emitted:
The stream-json parser uses JavaScript
\sin structural-whitespace patterns, which is broader than JSON's space/tab/CR/LF. Of 20 small cases, 15 agree with the strict oracle and five do not: BOM prefix, NBSP before a colon, vertical tab suffix, form-feed suffix and thin space before a comma. BOM tolerance may be a deliberate interoperability policy; the internal NBSP/thin-space cases provide the less ambiguous grammar mismatch. The observation is about compatibility with the stated JSON.parse oracle, not evidence of a security exploit or the original memory bug.Local review candidate
A small incremental byte guard before the parser rejects non-ASCII bytes and non-JSON controls outside quoted strings, retaining only quoted/escaped state across chunks. Inside strings it leaves UTF-8 and escapes to the actual parser. It is not a replacement JSON parser and does not collect the whole line.
The unchanged reference reader and the local derived candidate were both tested with Node 24.19.0 and
--max-old-space-size=64:Tests and the independent guard:
reader-audit/reader-probe.cjs,reader-audit/strict-json-syntax.cjs. The candidate builder is hash-guarded. Dependencies were fetched from their pinned npm releases with SHA-512 validation; no package lifecycle or posted plugin-integration script was executed.Before adopting the upstream streaming proposal, please make the intended malformed-record contract explicit and add this counterexample. The separate product work still needs verification of cancellation/disposal, scheduling/coalescing, file replacement, lifecycle callbacks and bounded memory on representative workloads. These tests do not cover all those properties and do not imply that #38611 is fixed.
Reference reader authorship remains with jsegeren and its dependencies' authors. Alexey Salkutsan selected this investigation; Lina, an AI assistant, prepared and ran the independent tests and authored the small strict-syntax guard. No runtime patch was installed.
Reproducer package
Download the synthetic reproductions and reader audit. The same small packet supports this issue and the related investigations in #43017, #43129 and #38611. It contains no personal histories, databases, raw RPC logs or bundled executable. Original third-party reader code is fetched by the setup with a pinned hash and attribution.
Verified after extraction into a clean directory on macOS arm64 with Node 24.19.0. Read
README_EN.md, then runnode bootstrap.mjsandnode verify.mjs --portable; setup downloads pinned public dependencies and the official binary. The clean verification used the already downloaded, hash-verified official binary archive with--archive; it fetched the reader and npm dependencies afresh. Linux, Windows and the uncached binary-download branch were not tested. The ZIP contains the earlier preparation drafts, whose NOT SENT headings describe the pre-publication snapshot.ZIP SHA-256:
2d60212455f72dddfd52410b3c8cc55e826ba2900e0813e26c070d65566972fb.