Windows app: pasted clipboard images spin forever in history after Temp files are deleted

Open 💬 3 comments Opened Jul 13, 2026 by 74726
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of the Codex App are you using (From “About Codex” dialog)?

26.707.8168.0

What subscription do you have?

Paid ChatGPT subscription (exact tier not included in this report).

What platform is your computer?

Microsoft Windows NT 10.0.26200.8655 x64 (Windows 11 25H2)

What issue are you seeing?

Images pasted from the Windows clipboard are processed successfully when the message is sent, but older images in the same Codex Desktop thread later display an infinite loading spinner.

A local inspection of the affected rollout JSONL shows two representations of each pasted image:

  1. The response_item message contains an embedded input_image with a complete data:image/png;base64,... URL.
  2. The corresponding event_msg.user_message contains only a local_images entry pointing to:

C:\Users\<user>\AppData\Local\Temp\codex-clipboard-<uuid>.png

In the affected thread:

  • 17 unique clipboard images were sent.
  • All 17 have embedded data:image content in the rollout JSONL.
  • 16 of the 17 referenced codex-clipboard-*.png Temp files no longer exist.
  • The only image whose Temp file still exists is the newest image.
  • The missing historical images show an infinite spinner in the UI.
  • The model successfully processed the images at send time, so this is not an upload or inference failure.

This strongly suggests that the history renderer uses event_msg.payload.local_images paths and does not fall back to the already persisted embedded image data when the temporary clipboard files have been cleaned up.

No API gateway, account, or inference error is involved.

What steps can reproduce the bug?

  1. Open the Codex Windows desktop app.
  2. Paste a screenshot from the clipboard into a thread.
  3. Send the message and confirm that Codex can process the image.
  4. Repeat with several screenshots.
  5. Restart Codex and/or allow the Windows Temp directory to be cleaned.
  6. Reopen the thread and scroll to the older pasted screenshots.
  7. Observe that the image placeholders spin indefinitely when their codex-clipboard-*.png files no longer exist.

Diagnostic confirmation:

  • Inspect the rollout JSONL under ~/.codex/sessions/.
  • The message has embedded input_image Base64 data, but the UI-facing event retains a path under %LOCALAPPDATA%\Temp.
  • Delete or lose that Temp file, then reopen the thread.

What is the expected behavior?

Pasted image attachments should remain visible in thread history after restarting the app or after Windows cleans the Temp directory.

The app should either:

  • copy clipboard images to durable app storage before persisting the message,
  • render from the embedded image data already stored in the rollout, or
  • show a clear missing-attachment error instead of an infinite spinner.

Additional information

The image data appears recoverable from the rollout JSONL, so the underlying content has not been lost. The failure is in attachment persistence/history rendering.

The affected paths and thread identifiers have been redacted. A screenshot of the spinner and additional redacted diagnostics can be provided if needed.

View original on GitHub ↗

3 Comments

github-actions[bot] contributor · 1 month ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #31590

Powered by Codex Action

grtninja · 1 month ago

A merged implementation in a separate Codex companion client provides a concrete version of the durable-storage fix proposed here.

grtninja/PennyGPT-Lite#7 handles image steers by:

  • validating and decoding bounded PNG/JPEG/WebP data URLs before the steer is accepted;
  • writing the image to an app-managed .codex attachment cache before invoking Codex;
  • passing the durable file as a real codex exec resume --image argument;
  • using private file permissions where supported;
  • retaining only lightweight attachment metadata/provenance in browser history instead of persisting the full base64 payload;
  • preserving the accepted attachment metadata across later thread refreshes;
  • refusing invalid/oversized images instead of silently sending text-only;
  • avoiding cache creation for text-only turns and refusing before persistence when the bridge is disabled;
  • applying bounded retention (14 days / 200 real image-turn directories in that implementation).

The merged PR was exercised by 113 server tests and 69 web tests plus lint/build/repository checks, and all attachment review threads were resolved, including durable pass-through, private permissions, refresh persistence, repeated same-text steer disambiguation, and base64/localStorage handling.

For Codex Desktop, the equivalent data model should make one attachment record authoritative rather than persisting two representations with different lifetimes:

attachment_id
mime_type
content_hash
durable_blob_or_path
embedded_fallback
source = clipboard
persistence_state

Suggested recovery/render order:

  1. durable app-managed blob/path;
  2. already-persisted embedded input_image data;
  3. typed attachment_missing card with repair/export action — never an infinite spinner.

During send, copy or materialize the clipboard image atomically before committing the UI-facing event. For existing affected rollouts, a migration/recovery pass can rebuild missing durable attachments from the embedded response_item.input_image data and rewrite the event reference. This is a submitted implementation reference, not a claim that the upstream app already uses the same storage path.

15230745073 · 4 days ago

Independent reproduction on a different Windows 11 machine, plus a few details that I think narrow the fix down.

Still reproduces at HEAD, and it is the steady state rather than an edge case

  • Desktop app 26.818.5345.0, Windows 11 26200.
  • Latest affected message: 2026-08-12, cli_version 0.147.0-alpha.6.6, originator: "Codex Desktop" — the event still records local_images: ["C:\Users\<user>\AppData\Local\Temp\codex-clipboard-<uuid>.png"] with images: [].
  • Scanned the whole local store (424 rollouts under ~/.codex/sessions + archived_sessions, spanning 2026-06-10 → 2026-08-12):

| | |
|---|---|
| sessions containing images | 32 |
| local_images refs total | 50 |
| refs under %TEMP% | 50 / 50 |
| refs still resolvable on disk | 0 / 50 |
| input_image base64 copies in the same rollouts | 196 |
| affected sessions recoverable from base64 | 18 / 18 (100%) |

So every pasted image older than the temp-cleanup window is broken, not just the ones in one long thread. On Windows 11 this is default-on behavior, not something the user did: Storage Sense ships enabled with "delete temporary files that my apps aren't using" (HKCU\...\StorageSense\Parameters\StoragePolicy, 01=1, 04=1), so %TEMP% is swept automatically.

Root cause is one function, and it is unchanged on main

ThreadHistoryBuilder::build_user_inputs maps the persisted event straight through with no existence check and no fallback:

https://github.com/openai/codex/blob/fb0781b9eee6d2da741b984bed9dde95834d909d/codex-rs/app-server-protocol/src/protocol/thread_history.rs#L1502-L1507

for (idx, path) in payload.local_images.iter().enumerate() {
    content.push(UserInput::LocalImage {
        path: path.clone(),
        detail: payload.local_image_details.get(idx).copied().flatten(),
    });
}

Verified present at main @ fb0781b (2026-08-24, after rust-v0.149.1). The model-facing path does degrade gracefully — Prompt::from_user_input falls back to local_media_error_placeholder when std::fs::read fails (codex-rs/protocol/src/models.rs) — but the UI-facing path has no equivalent, which is why the result is an infinite spinner instead of an error card.

Also worth noting: nothing under codex-rs/ ever copies an image into CODEX_HOME. The TUI writes clipboard images to the system temp dir via tempfile() (codex-rs/tui/src/clipboard_paste.rs, prefix codex-clipboard-), and the desktop frontend writes a UUID-named file to the same place. The durable copy is a design gap on the write side, not a cleanup race.

The durable attachment store already exists — it is just text-only

~/.codex/attachments/ is already used today, but only for pasted text:

~/.codex/attachments/<uuid>/pasted-text.txt
~/.codex/attachments/pasted-text-attachments.json   # index with excerpts + pendingRemovalPaths

Pasted images could reuse exactly this directory and lifecycle instead of %TEMP%, which makes the "copy to durable app storage" option in the original report considerably cheaper than it looks — no new storage mechanism, retention policy, or migration surface is needed.

There is already a precedent for the render-side half, in the same function

#34385 ("Preserve audio across history and tool outputs", 6f78563, 2026-07-20) added exactly this kind of preservation for audio in thread_history.rs, including local audio in legacy user-message events. Images never got the same treatment. Making LocalImage fall back to the already-persisted response_item input_image data URL when the path is gone would bring images to parity and would fix existing rollouts, not just new ones.

Existing data is fully recoverable

Because the base64 copy is always present in the response_item, a migration pass can rebuild every missing attachment with no data loss — 196/196 images in my store, covering 18/18 affected sessions. I extracted them all with a ~40-line script that walks the rollouts and decodes payload.content[].image_url; happy to attach it if useful.

Possibly relevant to triage

#31590 ("Codex app stores pasted image file paths instead of copying bytes") was closed as completed on 2026-08-09, but the 2026-08-12 messages above still recorded a %TEMP% path in local_images. Whatever landed there does not appear to cover the desktop paste path or the history-rendering half tracked here.