Skip to content

desktop: pasted image breaks and send fails with "Failed to fetch" after the clipboard temp file is deleted #62052

Closed

Description

@lfscqk67

Description

Pasting an image that the clipboard hands over as a temp file (on Windows, WeType / 微信输入法 keeps clipboard images in %LOCALAPPDATA%Low\Tencent\WeType\ClipboardTmp and deletes the previous one on every new copy) works at first. Once that temp file is gone, the thumbnail turns into a broken image and sending fails with "Failed to fetch". Pasting two images in a row is enough: the first one always breaks.

The bytes are already saved to the draft store on paste, but the preview and blobDataUrl still read the original File. Chromium reads a disk-backed File lazily, so the send goes back to the deleted file. I checked this in plain Chromium: once the file is deleted, fetch(URL.createObjectURL(file)) throws TypeError: Failed to fetch and an <img> with that URL fails to load. An in-memory copy of the same bytes keeps working.

Plugins

none relevant

OpenCode version

Desktop 1.19.5. The same code path is on dev (packages/app/src/runtime/persistence/drafts.ts, putBlob / createBlobReference)

Steps to reproduce

  1. On Windows with WeType's clipboard enabled, copy image A and paste it into the prompt.
  2. Copy image B. WeType deletes A's temp file. Paste B.
  3. A's thumbnail is now broken. Press send and you get "发送提示失败 / Failed to fetch".

Screenshot

No response

Operating System

Windows 10 Pro 22H2

Terminal

n/a (desktop app)

Activity

  1. opencodejr commented on Nov 5, 2026

    @opencodejr

    Thanks for the detailed report and analysis. I can confirm the send failure.

    I ran the app's draft-store code (packages/app/src/runtime/persistence/drafts.ts) in headless Chromium. I didn't run the Windows desktop app or WeType. The steps:

    1. Load a PNG from disk as a disk-backed File and pass it to putBlob, as the composer does on paste.
    2. Check that blobDataUrl, the function used when sending, returns the image.
    3. Delete the file on disk.
    4. Call blobDataUrl again. It now fails with NotFoundError: A requested file or directory could not be found…, even though the draft store still holds the copied bytes.

    The retained blob and object URL point at the original File rather than the copy that was saved, as you described. It happens on v2.2.5 and on the latest dev (893e8dc). In my headless run the thumbnail kept showing (probably Chromium's image cache), so I could only confirm the send failure, not the broken thumbnail. The script below reproduces it.

    Reproduction script (repro.mjs)
    // Usage: node repro.mjs <opencode checkout>  (bundles packages/app drafts.ts for the browser and runs it in Chromium)
    import { chromium } from "playwright"
    import { execSync } from "node:child_process"
    import fs from "node:fs"
    const repo = process.argv[2] ?? "/workspace/repo"
    const dir = "/workspace/repro"
    fs.copyFileSync(`${dir}/entry.ts`, `${repo}/packages/app/src/__repro_entry.ts`)
    execSync(`cd ${repo}/packages/app && bun build src/__repro_entry.ts --target browser --outfile ${dir}/out/bundle.js`, { stdio: "inherit" })
    fs.rmSync(`${repo}/packages/app/src/__repro_entry.ts`)
    fs.writeFileSync(`${dir}/out/index.html`, `<input type=file id=f><script src="bundle.js"></script>`)
    // 1x1 PNG standing in for the clipboard temp file (WeType ClipboardTmp)
    const tmp = "/tmp/opencode/ClipboardTmp-A.png"
    fs.writeFileSync(tmp, Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64"))
    const browser = await chromium.launch({ args: ["--allow-file-access-from-files"] })
    const page = await browser.newPage()
    await page.goto(`file://${dir}/out/index.html`)
    await page.setInputFiles("#f", tmp) // disk-backed File, like a pasted clipboard temp file
    await page.evaluate(() => paste(document.querySelector("#f").files[0]))
    console.log("before delete:", await page.evaluate(() => check()))
    fs.unlinkSync(tmp) // the IME deletes the temp file on the next copy
    console.log("after delete: ", await page.evaluate(() => check()))
    await browser.close()

    Run it: bun repro.mjs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions