Skip to content

Desktop threads can be poisoned by inline base64 tool images, leading to {"detail":"Bad Request"} on resume and likely inflated token usage #18629

Open

Description

@mikezio

Summary

Codex desktop appears to persist image-producing tool output inline into replayable session history, especially as function_call_output items containing input_image payloads with data:image/...;base64,... content.

After enough of these accumulate, the thread can become unstable and start failing on later turns with:

{"detail":"Bad Request"}

This also appears likely to inflate model input usage for the affected thread because the replay payload becomes extremely large.

User-visible symptoms

  • A thread works normally for a while, then later starts failing with:
    {"detail":"Bad Request"}
  • The agent stops mid-session or fails on later continue attempts.
  • Once a thread reaches this state, it may keep failing repeatedly.
  • The affected thread/session becomes extremely large.

What I found locally

I inspected saved session JSONL data under ~/.codex/sessions/... and found many oversized function_call_output entries containing embedded inline image payloads such as:

  • type: "input_image"
  • image_url: "data:image/png;base64,..."

These image payloads are being persisted directly into replayable session history rather than stored out-of-line.

In the affected thread I observed:

  • repeated event_msg errors with:
    {"detail":"Bad Request"}
  • many very large function_call_output records containing base64 images
  • very large cumulative input-token counters in the same thread
  • app-state telemetry showing very large delta-byte growth around the same period

Likely root cause

The desktop app seems to serialize tool screenshots inline into session state.

A strong lead is in the bundled browser-use plugin code shipped with the app bundle. In the installed app I found code in both:

  • /Applications/Codex.app/Contents/Resources/plugins/openai-bundled/plugins/browser-use/scripts/browser-client.mjs
  • /Applications/Codex.app/Contents/Resources/plugins/openai-bundled/plugins/chrome/scripts/browser-client.mjs

that converts screenshot buffers into inline data URLs before emitting them:

processImage:t=>{
  if(!Buffer.isBuffer(t)) throw new Error(...);
  return `data:image/jpeg;base64,${t.toString("base64")}`
}

That means screenshot-heavy tool output can enter replayable thread state as huge inline strings.

Why this matters

This is more than a display bug:

  • it can permanently poison a thread
  • it can trigger replay/resume failures with 400 / {"detail":"Bad Request"}
  • it likely wastes context/token budget if these payloads are replayed or summarized into later requests
  • it probably affects any user doing longer sessions with image-heavy tools (Computer Use / browser-use / screenshot-producing tools)

Repro idea

  1. Start a long Codex desktop thread.
  2. Use Computer Use / browser-use or another screenshot-producing tool repeatedly.
  3. Continue the same thread many times.
  4. Inspect the saved session JSONL and observe large input_image / data:image/...;base64,... payloads inside function_call_output.
  5. Eventually the thread starts failing with:
    {"detail":"Bad Request"}

Expected behavior

Tool images should not be embedded as large base64 strings in replayable session history.

Instead, Codex should:

  • store image blobs out-of-line
  • persist only a file/blob reference or lightweight handle
  • cap / summarize oversized tool outputs before they enter replayable thread state
  • reject or sanitize replay-hostile payloads during persistence and/or resume

Actual behavior

Large inline base64 screenshots are persisted directly in function_call_output, and the thread later fails with:

{"detail":"Bad Request"}

Suggested fix

  • externalize tool images to blob/file storage before persistence
  • persist only lightweight references in session history
  • add hard caps/truncation for replayed tool output
  • prevent megabyte-scale inline payloads from being stored in replayable thread context
  • consider a repair/sanitization path for already-poisoned sessions

Notes

I also suspect this bug may contribute to inflated subscription/token usage for affected threads because the stored payload becomes massive and may be replayed to the model on later turns.

If helpful, I can provide a sanitized example session structure showing the oversized function_call_output / input_image pattern without exposing private data.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    tool-callsIssues related to tool calling
    on Apr 20, 2026
  2. removed
    appIssues related to the Codex desktop app
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    on Apr 20, 2026
  3. cnjimmyshao commented on Apr 26, 2026

    @cnjimmyshao

    I hit the same class of issue on macOS with Codex Desktop, with an additional failure mode that blocks startup / verification.

    Environment:

    • Codex.app: 26.422.30944, CFBundleVersion 2080
    • Bundled app-server reported: 0.125.0-alpha.3
    • Electron: 41.2.0
    • Platform: macOS arm64

    Observed symptoms:

    • Codex Desktop became stuck during verification/startup.
    • Main-process dialog:
      RangeError: Invalid string length
      at Mu.write / Socket.onStdoutData
      in workspace-root-drop-handler-B7KjFQ8M.js.

    Local evidence:

    • ~/.codex/sessions had grown to about 2.8 GB.
    • Three rollout JSONL files were abnormal: about 1.4 GB, 1.3 GB, and 60 MB.
    • Sampling the large JSONL lines showed image_generation_call / function_call_output records containing inline data:image/png;base64,... payloads.
    • A compacted history entry also embedded prior image payloads into replacement_history.
    • Desktop log showed:
      Failed to parse MCP message
      with lineBytes=537005464
      for a thread/read response.
    • Moving those oversized rollout files out of ~/.codex/sessions reduced the active session directory to about 12 MB and allowed Codex Desktop to start normally again.

    Expected:

    • Generated/tool images should be stored out-of-line or referenced by blob/file handles.
    • thread/read should not emit hundreds of MB in a single stdout JSON line.
    • Desktop should cap/skip/repair oversized sessions instead of hanging during verification or throwing a main-process RangeError.

    Workaround confirmed:

    • Quarantine the oversized rollout JSONL files outside ~/.codex/sessions, then restart Codex Desktop.
  4. neokry commented on May 5, 2026

    @neokry

    I saw a closely matching failure in Codex Desktop on macOS while testing a local Vite app.

    Flow:

    • Spawned a subagent to playtest http://127.0.0.1:5123/
    • It loaded the page with Playwright and interacted successfully through visible UI/roles
    • It opened the in-game "Lab Manual" modal successfully
    • Then it called page.screenshot() and emitted it with nodeRepl.emitImage({ bytes, mimeType: 'image/png' })
    • The session log then stored a very large function_call_output containing an input_image with an inline data:image/png;base64,...
    • The agent stopped making progress after that image output and had to be shut down after ~3 minutes

    This happened in a fresh subagent thread, so the stall was not caused by a long existing chat history. The symptom strongly suggests a single inline screenshot payload can be enough to destabilize or heavily inflate the active thread, not only repeated screenshots over a long session.

  5. Astro-Han commented on May 8, 2026

    @Astro-Han

    I hit the same underlying issue with Codex App's built-in imagegen, not browser-use or computer-use screenshots.

    Environment:

    • Codex App: 26.506.21252 (2575)
    • Platform: Darwin 25.4.0 arm64 arm
    • Plan: omitted

    Observed failure mode:

    • After restarting Codex App, it restored an old image-heavy session and became CPU-heavy / stuck.
    • Moving the oversized rollout JSONL out of ~/.codex/sessions and restarting Codex App stopped the high CPU.
    • While the app was still running, app-server still held an open file descriptor to the moved rollout file, so the full restart was needed to release it.

    Sanitized local evidence:

    • Affected rollout JSONL size: 235 MB
    • JSONL lines: 4,256
    • Largest single JSONL line: 9,455,323 bytes
    • Event/type counts:
      • function_call_output: 745
      • function_call: 745
      • image_generation_call: 29
      • event_msg: 1,877
      • turn_context: 86
      • compacted: 7
    • The largest sampled records were response_item / function_call_output entries containing inline image payloads. The output payload length was about 9,455,166 chars.
    • Built-in imagegen payloads also contributed heavily:
      • image_generation_call records: 29
      • total image_generation_call.result chars: 90,454,556
      • max single image_generation_call.result chars: 3,924,372
    • The same imagegen payloads also appeared in image_generation_end event messages:
      • image_generation_end records: 29
      • total image_generation_end.result chars: 90,454,556
      • max single image_generation_end.result chars: 3,924,372

    So this appears to affect Codex App's built-in imagegen path as well, not only screenshot-producing tools. It also overlaps with #21211 because restoring/opening the large image-heavy session caused high CPU during history hydration.

    Expected:

    • Large image/tool outputs should not be stored inline in replayable session JSONL.
    • Store image blobs out-of-line as artifacts and keep only metadata/references in JSONL.
    • Avoid duplicating large image payloads across event_msg and response_item surfaces.
    • Session restore should lazy-load heavy image/tool payloads or skip/repair oversized sessions instead of eagerly parsing/rendering them.

    I am intentionally omitting local paths, session id, prompts, image data, workspace names, and external service content for privacy.

  6. cnjimmyshao commented on May 9, 2026

    @cnjimmyshao

    I took a quick look at the current main code path and I think this issue has a fairly crisp persistence-layer shape.

    The recurring pattern seems to be:

    • UserInput::Image / UserInput::LocalImage become ContentItem::InputImage { image_url: "data:image/...;base64,..." } in codex-rs/protocol/src/models.rs.
    • RolloutRecorder::record_items keeps persisted ResponseItems mostly as-is.
    • JsonlWriter::write_rollout_item serializes the RolloutItem directly to JSONL, so any inline image data URL in:
      • ResponseItem::Message
      • FunctionCallOutput / CustomToolCallOutput content items
      • ImageGenerationCall.result
      • CompactedItem.replacement_history
        can become a very large JSONL line.
    • EventMsg::ImageGenerationEnd is also persisted in limited mode, so generated image payloads can be duplicated across response and event surfaces.

    I think the durable fix should happen before rollout serialization, not only in Desktop thread rendering. Otherwise existing UI pagination or lazy rendering can reduce symptoms while new rollouts still accumulate replay-hostile inline blobs.

    A focused first PR could be:

    1. Add a rollout attachment store under CODEX_HOME, probably content-addressed by sha256 and scoped by thread/session id.
    2. Before writing rollout JSONL, rewrite inline data:image/*;base64,... payloads into small internal attachment references.
    3. Keep old inline JSONL readable for backward compatibility.
    4. Resolve attachment references only at the boundary that actually needs model-ready image bytes, rather than during thread list/read hydration.
    5. Add regression tests showing that image-heavy response items persist as small JSONL records while resume/model replay still works.

    I am deliberately framing this as a narrow storage fix rather than a broader Desktop performance PR. It should compose with thread pagination/lazy UI hydration work, but it addresses a different invariant: replayable rollout history should not store large image blobs inline.

    Would this direction align with the maintainers’ intended design? If so, I would be happy to put together a small invited PR limited to the rollout persistence/read compatibility path.

  7. dfranklin-at-bg commented on May 11, 2026

    @dfranklin-at-bg

    Another potential occurrence using the Box plugin. A bug in Box plugin caused the session to be poisoned. Codex could not even answer a basic question like "what time is it". (I have not yet logged a bug against the plugin). Output from Codex looked like this:
    { "type": "error", "error": { "type": "invalid_request_error", "code": "invalid_value", "message": "Invalid 'input[111].output[1].image_url'. Expected a base64-encoded data URL with an image MIME type (e.g. 'data:image/ png;base64,aW1nIGJ5dGVzIGhlcmU='), but got empty base64-encoded bytes.", "param": "input[111].output[1].image_url" }, "status": 400 }

    Here's what the Box plugin did:

    box_get_preview_page({"fileId":"2212855813609","page":1}) returned a successful tool result with an empty PNG:

    {"type":"image","data":"","mimeType":"image/png"}

    Codex then serialized that into the resumed transcript as:

    {"type":"input_image","image_url":"data:image/png;base64,","detail":"high"}

    That entry is invalid because the base64 payload is empty. On every later turn in that same resumed session, Codex replayed the transcript, hit the same bad image_url, and the API rejected the whole request before the model could answer anything.

    The practical workaround is: don't resume that session. Start a fresh Codex session, or resume from an earlier/unaffected session.

    Although the Box plugin errantly returned an empty image as a successful result, Codex should not allow the session to be poisoned. Codex should sanitize or reject empty image tool outputs before saving/replaying them.

  8. DawgZter commented on May 15, 2026

    @DawgZter

    Adding another concrete repro/repair datapoint for Codex Desktop.

    Feedback ID from in-app flow: 019e20c6-fe64-7e92-8e5f-9260d5827531

    Affected session:

    • Session ID: 019e17c4-3951-78f2-a6b4-ce9886b5eee7
    • Thread name: command analysis
    • App context: long-running Codex Desktop session using screenshot/image-producing workflows

    What happened:

    • The session repeatedly became unusable after inline image outputs were written into the saved JSONL transcript.
    • Removing one bad image was not enough; after continuing the thread, the problem came back when more image blocks were produced.
    • I eventually made the session text-only as a local workaround.

    Local repair evidence:

    • Multiple bad structured image blocks had to be removed over time.
    • One image decoded from base64 but failed image validation with a truncated-image read.
    • Final workaround stripped all structured image blocks from the affected session transcript:
      • removed 380 structured image/tool-result image blocks
      • changed 342 JSONL lines
      • verified 0 remaining input_image, image, data:image, or image_url markers
      • verified the repaired JSONL parsed cleanly: 85,065 lines, 0 JSON parse errors
    • I also moved repair backups out of ~/.codex/sessions because leaving backup .jsonl... files near live sessions looked risky if the app scans/globs that directory.

    User-visible impact:

    • A single corrupt or replay-hostile image payload can make a valuable long-running thread effectively unusable.
    • The user has to manually edit local session JSONL to recover.
    • The same session can re-break after repair if the agent/tooling emits another image block.
    • The current practical workaround is to tell the agent not to use image(...), view_image, browser screenshots, Markdown image syntax, or data:image/... URLs, which is fragile.

    Requested product behavior:

    • Validate/sanitize image payloads before persisting them to replayable session history.
    • On load/resume, skip corrupt/truncated/oversized image payloads and replace them with a placeholder instead of breaking the thread.
    • Store images out-of-line as blobs/files/handles rather than inline base64 inside JSONL.
    • Add a built-in "repair session / strip broken media" option.
    • Add a real text-only/no-images mode that screenshot-capable tools and agents respect.
    • Ensure non-live backup files in the sessions tree are not accidentally treated as active sessions.
  9. Necmttn commented on Jun 21, 2026

    @Necmttn

    Image/tool binary payloads should be externalized before they enter replayable history.

    Store a content-addressed blob once, then put a lightweight content_ref in the transcript with media type, byte count, hash, producer tool id, redaction status, and retention policy. Replay can decide whether to hydrate the blob, summarize it, or omit it.

    That also makes token-cost attribution auditable.


    Generated with ax.

  10. Electro-Dig commented on Jul 15, 2026

    @Electro-Dig

    Adding a Windows reproduction with stronger evidence that repeated compaction amplifies the inline-image storage problem, rather than merely preserving each image once.

    Environment:

    • Codex Desktop: 26.707.9981.0 (Microsoft Store)
    • Codex CLI: 0.144.1
    • Windows 11 x64, build 26200

    Sanitized measurements from the largest affected rollout:

    rollout size:                         2,408,117,275 bytes (~2.24 GiB)
    JSONL records:                        17,591
    compacted records:                    47
    bytes in compacted records:           ~2,118.5 MiB
    response_item records / bytes:        11,357 / ~137.7 MiB
    event_msg records / bytes:            5,943 / ~39.9 MiB
    records containing image_url:         134
    records containing base64:            148
    largest compacted JSONL line:         ~53.7 MiB
    

    I parsed one of the largest compacted records structurally. It contained 52 fields under paths like:

    payload.replacement_history[*].content[*].image_url
    

    All were data:image/png;base64,... values. Those 52 image URL fields totaled 56,180,605 characters in that single compacted record; the largest individual values were about 3.16M, 2.03M, and 2.01M characters.

    Two other recent rollouts were ~1.61 GiB and ~1.56 GiB and showed the same shape: most bytes were in compacted, with historical inline image URLs copied into replacement_history. The active sessions directory gained roughly 8.36 GiB in seven days during screenshot/image-heavy work.

    This looks like multiplicative persistence amplification:

    1. screenshot/image output enters replayable history as an inline data URL;
    2. later compaction serializes a replacement history that still contains those data URLs;
    3. repeated compactions append new large copies to the rollout;
    4. the rollout therefore grows by tens of MiB per compaction even when the underlying images are old.

    Current main appears consistent with this behavior in codex-rs/core/src/compact_remote_v2.rs:

    • build_v2_compacted_history retains user/developer/system messages and clones them into replacement history;
    • message_text_token_count charges ContentItem::InputImage zero tokens (the enclosing message is only charged the later .max(1));
    • truncate_message_text_to_token_budget pushes every InputImage unchanged;
    • regression tests explicitly assert that retained-history truncation preserves images.

    This means one retained message containing dozens of multi-megabyte data URLs can cost roughly one unit of the text budget while adding tens of MiB to every persisted compaction.

    A durable fix likely needs both:

    • externalizing/deduplicating image blobs before rollout persistence, with backward-compatible reads; and
    • ensuring CompactedItem.replacement_history stores lightweight references or sanitized model context rather than cloning historical base64 payloads.

    A smaller defensive change could add an image count/byte budget when building compacted history, retain only the newest bounded images, and add a regression test asserting that repeated compaction of image-bearing history does not grow rollout bytes linearly with the full image payload.

    I have archived old rollouts as a local workaround. I am not attaching raw JSONL because it contains private conversation and image data.

  11. penguinspecz commented on Jul 20, 2026

    @penguinspecz

    Additional macOS A/B evidence showing both history amplification and downstream impact:

    Measured rollout-family amplification

    I classified every local rollout by its first session_meta record and followed source.subagent.thread_spawn.parent_thread_id to its root.

    • Root family 019f5db5…: 198 files / 29,480,944,591 bytes (27.456 GiB)
    • Root family 019f48a5…: 248 files / 5,445,233,918 bytes (5.071 GiB)
    • Combined: 446 files / 34,926,178,509 bytes (32.527 GiB)
    • Largest root rollout: 825,815,139 bytes, 70,181 JSONL records
    • That root included 217 data:image records, 216 input_image records, and a largest single JSONL record of 9,958,819 bytes
    • Many archived children were 380–645 MB. Their metadata identified them as depth-1/depth-2 subagents of the same root, and they contained replayed parent history before their own turns.

    This means one long-lived image/tool-heavy task does not merely create one large audit file: subagent lineage can multiply the persisted parent payload across hundreds of files.

    Downstream controlled test

    OpenUsage 0.7.6 scans both ~/.codex/sessions and archived_sessions to estimate Codex usage. With these histories present:

    • OpenUsage showed 20.03 GB in the macOS process list
    • measured peak physical footprint was 21.8 GB
    • its initial Codex refresh took 66.265 seconds

    I then quit OpenUsage and atomically moved only the two affected archived families outside the scanned directories. All 446 files retained the same device, inode, and byte size at the quarantine destination.

    Afterward:

    • ~/.codex fell from about 35 GB to 2.6 GB
    • OpenUsage relaunched and remained at a 234 MB physical footprint during a 25-second initial-scan sample

    I am filing the downstream whole-file parsing problem separately with OpenUsage, but the A/B result demonstrates that the Codex rollout shape is hazardous to downstream consumers even when they only need token_count records.

    Quota observation

    During the same period, a weekly Codex allowance was consumed in roughly 28 hours (Sol 5.6 High). While Sol is known to consume rate limits quite fast, this still makes context amplification a plausible contributor, but local JSONL size alone does not prove that every persisted byte was submitted to or billed by the model. Server-side request accounting would be needed to confirm causality.

    Requested persistence/consumer contract

    In addition to externalizing image blobs, please consider:

    1. Child rollouts should reference parent lineage/history by ID and immutable cursor, not copy parent records into every child file.
    2. Durable audit history, model replay context, UI transcript, and binary artifact storage should be separate bounded representations.
    3. Provide a stable, compact, versioned local usage-event stream so accounting tools do not need to parse full-fidelity replay/audit logs.
    4. Add a supported repair/cold-storage path for already-amplified task families.
    5. Publish whether compaction/replay excludes persisted image/tool payloads and expose request-side context accounting by category.

    Screenshot and a sanitized downstream report are available; no prompt text, source code, auth data, or raw image payloads need to be shared.

  12. jkammerland commented on Jul 21, 2026

    @jkammerland

    Hit same bug on codex-cli 0.144.6. Viewing four 1024×1024 PNGs created one 22.4 MB inline tool result. WebSocket disconnected,
    HTTPS fallback started, then every prompt in that thread returned {"detail":"Bad Request"}. Session JSONL remains valid; only
    thread is poisoned. Looks related to #24550 and #28316.

  13. waveblink commented on Aug 16, 2026

    @waveblink

    I am seeing a closely related failure on Windows in Codex Desktop.

    Observed behavior:

    • A project task can run normally for several minutes, then terminate with {"detail":"Bad Request"}.
    • I also see stream disconnected before completion / WebSocket closed by server before response.completed reconnect failures.
    • This is not limited to one old task: a newly created repair task also failed with the same Bad Request before making any edits.
    • Several affected tasks involved local browser/computer-use inspection and screenshots.

    Sanitized local evidence:

    • One affected task rollout was about 62 MB and had accumulated roughly 19 million input tokens by the time repeated Bad Request failures appeared.
    • Other fresh project tasks also failed after short runs, so simply starting a new chat is not a reliable workaround.
    • No raw transcripts, screenshots, workspace paths, account identifiers, or session IDs are attached for privacy.

    This seems consistent with the possibility that image/tool output or a related streaming/compaction path can destabilize a task. It would be especially useful if the app could surface a more specific rejection reason and safely recover the task rather than leaving the user with only a generic Bad Request.

  14. yyysheng commented on Aug 25, 2026

    @yyysheng

    Additional sanitized Windows Desktop reproduction, with a compaction-specific failure mode.

    Environment:

    • Codex Desktop on Windows
    • App build observed: 26.818.8289.0
    • Long-running visual/tool-heavy thread using repeated screenshot-producing tool calls

    Observed sequence:

    1. The thread accumulated many tool outputs and image-review records.
    2. Immediately before the failure, local diagnostics showed an approximately 65.9 MB serialized request body, about 39.2 MB after zstd compression.
    3. Token telemetry at the same stage was approximately 233k total usage tokens with an auto-compaction scope limit of approximately 245k, while both full_context_window_limit_reached=false and token_limit_reached=false were reported.
    4. The UI indicated that automatic context compaction had been triggered.
    5. The remote compact operation itself then failed with:
    Error running remote compact task: {"detail":"Bad Request"}
    
    1. Retrying inside the same thread was ineffective because the next request/compact attempt replayed the same oversized history. A fresh thread using only a short handoff of current state and required file references could continue normally.

    This suggests a recovery deadlock: the thread becomes large enough that it needs compaction, but the remote compaction request itself is rejected before it can shrink the history. In this case the limiting factor appears to be serialized request size / replayed tool payloads rather than model context-window exhaustion.

    This looks closely related to #11478 / #11440, except the current Desktop failure surfaces as generic HTTP 400 / {"detail":"Bad Request"} rather than a visible 413.

    Expected behavior:

    • Externalize or omit large inline image/tool payloads before remote compaction.
    • Apply a byte-size budget in addition to token-based compaction thresholds.
    • If remote compaction fails because the payload is too large, automatically create a lightweight recovery handoff/new thread instead of repeatedly replaying the same unrecoverable history.
    • Do not retry the identical oversized request multiple times.

    Privacy note: I intentionally omitted local filesystem paths, OS username, project/workspace names, prompts, image contents, thread/session IDs, request IDs, and other identifying content. Only rounded aggregate size/token measurements are included.

  15. chipfighter commented on Sep 28, 2026

    @chipfighter

    A workaround for desktop users while this is open. My case had the same shape on the Windows desktop app: I was iterating on image generation with GPT in one long task, and after a dozen or so images every request carried all of them again, about 44 MB each. The connection kept dropping, the retries every few minutes never went through, and the only way out was a new task, which loses the conversation.

    So I built an open-source plugin for the Codex desktop app (disclosure: I'm the author): Codex Attachment Manager.

    • A tab in the side panel lists every image in the task, turn by turn, including the ones that came back from tool calls, with their sizes.
    • Uncheck the ones you no longer need. From the next message on, a local proxy replaces them with short placeholders before the request leaves your machine. If a thread is failing because of the image payload, this shrinks the next request without starting over. The rollout on disk isn't modified.
    • The model is told the images were left out on purpose. If it needs one back, it replies need IMG-xxx and you check it again.

    Limits: desktop app only; default OpenAI backend only (it won't install over your own openai_base_url); results of Codex's built-in image generation can't be unchecked. Tested on Windows with Codex 26.924. macOS and Linux only pass CI so far, so reports are welcome.

    Install it from Codex's Plugins page (add the marketplace chipfighter/codex-attachment-manager) or with a one-line command; see the README.

    Every image in the task is unchecked. Asked about v1's chat button, the model replies need IMG-002; the panel points to IMG-002, the user checks it, and the model answers from the image.

  16. m1r01848 commented on Oct 14, 2026

    @m1r01848

    Additional macOS Desktop reproduction: one 33.19 MB view_image tool result permanently poisons a thread

    I can reproduce this on a current macOS Codex Desktop installation. This case provides a fairly sharp boundary: a single custom tool result containing three inline images is immediately followed by permanent {"detail":"Bad Request"} failures.

    Environment

    • Codex Desktop: 26.1007.71326 (build 20398, production channel)
    • Bundled Codex CLI: 0.162.0-alpha.17.2
    • Platform: macOS on Apple Silicon
    • Model: gpt-5.6-sol, reasoning effort xhigh
    • Provider/wire API: OpenAI / Responses

    Privacy-safe measurements

    • Affected rollout JSONL: 128,200,371 bytes, 7,309 lines
    • Critical record: response_item/custom_tool_call_output, 33,185,463 serialized bytes
    • Producer: one exec tool call that invoked view_image for three local 1920x1080 PNG files
    • Persisted output: three input_image data URLs
    • Each image_url string: 11,061,594 bytes (Base64/ASCII)
    • Last successful model request before the critical tool output: 72,813 input tokens against a 258,400 token context window
    • Immediately afterwards: five stream disconnected before completion: websocket closed by server before response.completed retries, followed by an HTTP fallback
    • Fallback endpoint: POST /backend-api/codex/responses
    • Fallback response: HTTP 400, 24-byte body {"detail":"Bad Request"}
    • Every later text-only continuation of the same thread fails immediately with the same response
    • Local work products remain intact and a fresh thread works

    The installed codex doctor reports healthy state databases, successful Desktop app-server initialization, reachable provider endpoints, and a successful Responses WebSocket handshake. It also reports 472 active rollout files consuming about 2.45 GB, which is consistent with substantial persisted session data but not a general connectivity failure.

    Inference

    The timing and measurements point to a serialized request/tool-output byte limit rather than ordinary token-window exhaustion: the last successful request was well below the reported model context window, then a single 33.19 MB inline image result entered durable history, and no subsequent model usage record was created before the 400.

    This is also an aggregate-output problem, not only a per-image problem: each individual image data URL was about 11.06 MB, while the single combined tool result was about 33.19 MB.

    Expected behavior

    • Do not persist or replay raw multi-megabyte Base64 image bodies after the turn that consumed them.
    • Store a blob/file reference, hash, byte count, and compact placeholder in durable history.
    • Apply both per-item and aggregate byte limits to image-bearing tool results.
    • Before resume/compaction/request assembly, sanitize already persisted oversized image outputs.
    • If recovery is impossible, return an actionable client error that identifies oversized retained media instead of a generic HTTP 400.
    • Provide a supported repair path for already poisoned threads.

    I can provide the affected thread ID, failed turn IDs, request ID, timestamps, and the full local rollout through a private support channel. I am intentionally not attaching the 128 MB transcript or any raw image/prompt data publicly.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions