openai / openai/codex

Browser tool auto-attaches a screenshot to nearly every Code Mode result, permanently inflating history and app-server memory

Open
#40,175 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

app app-server browser bug performance
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:

  1. Rollout size. That session's rollout reached 306 MB in 6.5 hours; 91% of its bytes were base64 image data (650 images).
  2. 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.
  3. app-server memory. The app-server process 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.
  4. 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_SMALL live allocations (20.3 GB across 5188 regions) rather than MALLOC_LARGE (1.8 GB), so the memory is mostly parsed history structures, not the image blobs themselves.
What steps can reproduce the bug?
  1. Use a model whose metadata is tool_mode: code_mode_only (e.g. gpt-5.6-sol), with the bundled browser plugin enabled.
  2. 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.
  3. Inspect the resulting mcp_tool_call_end entry in the session rollout .jsonl.
  4. Its output array contains a data:image/png;base64,... item the script never requested.
  5. 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 browser plugin's browser-service.mjs and every NODE_REPL_* / BROWSER_USE_* string in the node_repl binary, and there is no disableScreenshot-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 imageDetail default 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.