anomalyco / anomalyco/opencode
[FEATURE]: TUI should capture dropped/pasted files at drop time when the source path is ephemeral
@simonklee is already working on this.
Since Sep 1, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Searched existing issues; several are adjacent, none covers this. Specifically:
- #46173 (managed per-session attachment store, assigned, PRs #46175/#46182/#46185 in flight) — solves the file-type half of this from the server side: path-only lowering, MIME sniffing, quotas, "unknown content stays path-only". This request does not duplicate that and does not re-argue file types. PR 3 wires web/desktop attach flows; the TUI is out of scope there, and the store is fed at submit time, which is too late for the case below. I've commented there with the same finding.
- #31939 (assigned) — Insert Path mode for external drops. Requests the opposite trade (reference the path, never inline) and assumes the dropped path is durable — exactly the assumption that breaks here.
- #21633 (PR, open) — writes pasted images to temp and switches
data:→file://. Explicitly states "non-image attachments are unaffected". - #27689 / #32102 / #32104 —
.docx/.xlsxdrops rejected. Type-allowlist framing; superseded by #46173's sniffing. - #35697 —
application/octet-streamattachments crash the session. Server-side crash, not TUI intake. - #34006 — Desktop vs Terminal pasted-path inconsistency. Doesn't address path lifetime.
- #14673 / #23801 / #17488 — pasted/dropped images lose their path.
Prior art, same root cause in another harness: anthropics/claude-code#65181 — a file shared via the macOS shared pasteboard lands on a path the agent cannot read (EPERM); the requested remedy is to copy it somewhere readable before substituting the path into the prompt.
Describe the enhancement you want to request
Summary
Dropping a non-image file into the TUI works from Finder and silently fails from every other application. The cause is not the file type — it's that the dropped path is ephemeral, and the TUI resolves it lazily, long after it has been reaped.
Request: classify the path at drop time and, when it is ephemeral or unreadable, capture the file immediately rather than storing a reference that is already dying.
Root cause
There is no drag-and-drop code in the TUI. Dropping a file on a terminal is handled by the emulator, which types the path into the tty; it arrives as an ordinary bracketed paste. Drop and paste therefore share one path: onPaste (packages/tui/src/component/prompt/index.tsx:1396) → pasteInputText → readLocalAttachment.
Raw bytes never arrive. A terminal injects text and nothing else. Any fix that hopes to receive the dropped bytes in the TUI cannot work — the only input is a path, and the only question is whether it still resolves when a tool finally reads it.
What differs between file types is when the bytes are read:
| Drop source | Behavior | Why |
|---|---|---|
| Image, any app | works | bytes read at drop time into a data: URL (index.tsx:1195-1201); the source path expiring never matters |
| Non-image, Finder | works | rejected as an attachment, so the path falls through as literal text (index.tsx:1215); Finder paths are durable, so Read resolves it later |
| Non-image, non-Finder | silently fails | same fall-through, but the path is a promise/pasteboard file that is gone or EPERM by the time the agent reads it |
The rejection is one line, packages/tui/src/component/prompt/local-attachment.ts:44:
if (!mime.startsWith("image/") && mime !== "application/pdf") return
MIME comes from an 8-entry extension map with no sniffing (local-attachment.ts:25-34). #46173 addresses that half; this issue is about the other half.
Steps to reproduce
- From a non-Finder source — an email attachment, Slack, an AirDropped file, a Safari download drag — drag a
.csv,.yaml, or.txtinto the TUI prompt. - A path like the following is inserted as literal text:
/Users/<me>/Library/Group Containers/group.com.apple.coreservices.useractivityd/shared-pasteboard/items/<uuid>/data.csv
- Ask the agent to read it.
Observed: the read fails — ENOENT once the promise file is reaped, or EPERM on the sandboxed shared-pasteboard path.
Expected: readable, exactly as the identical drop of a .png already is.
Verified on macOS (Darwin 25.x, Terminal.app / iTerm2). The $TMPDIR/TemporaryItems and shared-pasteboard directories are the two macOS sources I confirmed. Linux (XDG document portal) and Windows (INetCache) are likely equivalent but I have not verified them, so I'm not claiming them.
Proposed behavior
At drop/paste time, once a path is recognized:
- Durable and readable — Finder, a project file, anything outside the pasteboard/
TemporaryItems/portal dirs that passes anaccess()probe → keep current behavior, insert the real path. This is strictly better than copying: the agent can edit the original in place, and there's nothing to clean up. - Ephemeral or unreadable → capture the bytes immediately and attach the captured copy.
Detection should be location plus an access probe, never extension.
For step 2's destination, the managed store in #46173 is the natural home rather than a second parallel mechanism — but it currently expects upload at submit, and a drop can precede submit by minutes, which the source file will not survive. Failing that, a session-scoped temp dir referenced by file:// works today: packages/opencode/src/session/prompt.ts:808-889 already reads a file: FilePart through the Read tool with extra: { bypassCwdCheck: true } (line 822), so a path outside the project is explicitly permitted, and ?start=/?end= ranges already work. @file autocomplete builds exactly this shape (autocomplete.tsx:247-267).
Prefer file:// over data: for captured non-images: a data: URL inflates the prompt store, prompt-history.jsonl, and the wire payload by 4/3 for a file the model would rather read on demand.
Implementation notes
pasteAttachment(index.tsx:1224-1270) needs generalizing:virtualTextis hardcoded to[PDF n]/[Image n](line 1233) and the URL todata:(line 1251). A[File n]variant plus a matching counter predicate at lines 1228-1232.- Temp lifetime is the one genuinely new problem. The two existing TUI temp writes (
clipboard.ts:50,editor.ts:49) both delete in afinally; a captured attachment must outlive submit, because the server reads it lazily. Session-scoped, cleaned on session end/exit. - History replay.
PromptInfo.partsis persisted toprompt-history.jsonl, andrestoreExtmarksFromParts(index.tsx:658-700) will restore a placeholder pointing at a file deleted days ago. Recall should degrade visibly rather than silently attach nothing. - The canary test is
packages/tui/test/prompt/local-attachment.test.ts:32, which assertstext/plain→undefined; it should fail the moment the gate widens.
Why this is worth doing separately from #46173
The failure is silent and misattributed. The path lands in the prompt, the prompt submits, and the agent reports it cannot find a file the user is looking at on their own screen. Nothing errors at drop time, and nothing in the UI distinguishes the durable path from the doomed one. Users conclude drag-and-drop "only works for images" — when in fact it works for everything from Finder and nothing else from anywhere else.
#46173 will make the file types work. Without capture-at-drop-time in the TUI, this specific case still fails afterward, because by the time anything uploads, the file is gone.
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.
Assessment
This issue has not been assessed yet.