Browser tool auto-attaches a screenshot to nearly every Code Mode result, permanently inflating history and app-server memory
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What version of the Codex App are you using?
26.818.31338 (build 6892); codex-cli 0.149.0-alpha.4
What subscription do you have?
rate_limits payloads report plan_type: prolite
What platform is your computer?
macOS 26.4, Apple Silicon (arm64), 48 GB RAM
What issue are you seeing?
When the active model runs in code_mode_only mode (e.g. gpt-5.6-sol), every tool call is routed through the node_repl js tool. If the script touches the in-app browser at all, the tool result comes back with a full data:image/png;base64,... screenshot attached — even when the script never asked for one.
Measured over a single ~6.5 hour session:
node_repl :: js calls |
864 |
| calls whose result contained an image | 347 |
| of those, scripts referencing any screenshot/image API | 3 |
| images attached without being requested | 344 (99.1%) |
Example of a script that came back with an image attached — it only reads text:
const tab = await browser.tabs.new();
await tab.goto("http://127.0.0.1:<port>/");
await tab.playwright.waitForTimeout(1500);
nodeRepl.write(await tab.playwright.locator("body").innerText({ timeoutMs: 5000 }));
Downstream effects, all measured:
- Rollout size. That session's rollout reached 306 MB in 6.5 hours; 91% of its bytes were base64 image data (650 images).
- Compaction write amplification. Compaction itself works correctly — the retained-history snapshot rises and falls as expected. But each snapshot re-serializes the retained images. 64 snapshots wrote 188 MB to disk while live retained history peaked at 6.3 MB, i.e. roughly 30x write amplification.
- app-server memory. The
app-serverprocess reached 22.3 GB physical footprint after 3d20h uptime with 21 rollouts open, of which 21.2 GB was compressed/swapped, growing at roughly 5.5 GB/day. On a 48 GB machine this consumed essentially the entire compressor and filled swap. - Disk. The local sessions directory grew from 140 GB to 182 GB in 4 days (378 new rollout files).
Two notes for whoever triages this:
- RSS hides the memory growth. Resident was only ~1.0 GB because the rest was compressed;
vmmap --summary <pid> | grep "Physical footprint"is the metric that shows it. - The heap was dominated by
MALLOC_SMALLlive allocations (20.3 GB across 5188 regions) rather thanMALLOC_LARGE(1.8 GB), so the memory is mostly parsed history structures, not the image blobs themselves.
What steps can reproduce the bug?
- Use a model whose metadata is
tool_mode: code_mode_only(e.g. gpt-5.6-sol), with the bundledbrowserplugin enabled. - In Code Mode, run a script that only reads text from a page and never calls any screenshot or image API — for example the snippet above, against any local or public URL.
- Inspect the resulting
mcp_tool_call_endentry in the session rollout.jsonl. - Its
outputarray contains adata:image/png;base64,...item the script never requested. - Keep working in that session and watch rollout size and app-server footprint grow.
Session ID deliberately omitted — the conversations involved contain confidential work. Happy to supply one privately if it helps.
What is the expected behavior?
Any one of these would address it:
- Attach a screenshot only when the script actually requests one.
- Provide a switch to disable auto-attachment. I could not find one: I checked the bundled
browserplugin'sbrowser-service.mjsand everyNODE_REPL_*/BROWSER_USE_*string in thenode_replbinary, and there is nodisableScreenshot-style option. - Elide or downscale image payloads when writing compaction snapshots, so a retained image is not re-serialized on every subsequent compaction.
- Apply a low
imageDetaildefault to auto-attached images.
Additional information
These mitigations have independent value: the first fixes the source, while the compaction change fixes the ~30x amplification on its own and would still help if auto-attachment turns out to be intentional.
There is also no retention policy for the sessions directory, so all of the above accumulates permanently.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the bundled browser plugin's browser-service.mjs and the node_repl js path, then reproduce the text-only script and inspect the mcp_tool_call_end output in a session rollout .jsonl file. Done means unrequested scripts no longer attach screenshot data, with the resulting rollout and compaction growth checked against the reported behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100