Skip to content

Chat: pasting a copied file attaches it, like dropping it - #29089

Merged
brennanb2025 merged 25 commits into
mainfrom
brennanb2025/sta-10652-paste-copied-files
Oct 17, 2026
Merged

brennanb2025 merged 25 commits into
mainfrom
brennanb2025/sta-10652-paste-copied-files

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​309 $\color{#cf222e}{\Huge{\mathbf{−}}}$​211 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​98
Prod 13 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​574 $\color{#cf222e}{\Huge{\mathbf{−}}}$​200 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​374

ELI5

If you copied an image file in Finder and pasted it into Chat, Chat typed the file's name into the message box and attached nothing. Now pasting a copied file attaches it, exactly as dragging the same file onto Chat does.

What Changed

The problem. A user reported in Discord that they couldn't paste images into Chat. A live reproduction on current main showed:

  • Pasting a screenshot copied to the clipboard (picture data) worked.
  • Pasting an image file copied in Finder typed shot.png and attached nothing. No message explained why.

Why it happened. Chat's paste only attached a picture read from the clipboard. A Finder copy carries the file's location and its name as text, but on macOS no picture of the file. So the save step found nothing, and Chat fell back to typing the name, on purpose ("a copied file that brings no image still types its name"). On 1.4.224, a copy that carried both a file location and a PNG also attached nothing; #27346 fixed that case in 1.4.225.

Before / after, as the user sees it

You copy, then paste into Chat Before After
A screenshot (picture data) Attached Attached (unchanged)
An image file in Finder / Explorer / a Linux file manager Its name was typed; nothing attached (macOS). Elsewhere, the picture data was attached when the clipboard had it. The file is attached
A non-image file (PDF, text file) Its name was typed The file is attached, as a drop would
Text that isn't just the copied files' names Typed Typed (unchanged)
A copied file into a chat that can't take local files (e.g. a terminal chat on a paired server) Name typed, or a notice The same notice a drop shows

The mechanism. Chat already has one route for files from outside: the drop route (captureExternalDrop, behind attachExternalPaths), used for drag-and-drop and the file picker. It checks the files can be read, uploads them when the chat runs on a paired Orca server, and shows the existing notice when the chat can't take local files. Pasting now uses that route whenever the clipboard holds copied files and nothing else (no text, or only text that names those files):

  • App-menu paste (Cmd+V on macOS, right-click Paste): the paths come from main's existing readClipboardFilePaths, which already reads copied files on macOS, Windows and Linux. Before, Chat only used those paths to decide whether the text was just a label.
  • Paste event (Ctrl+V on Windows and Linux): each pasted file's path is resolved with getPathForFile, the same call the drop handler uses. A pasted picture has no path, so it keeps the existing picture flow.

When copied files are found, the picture on the clipboard is ignored. A file manager's picture can be the file's icon, not the file itself.

Screenshot tools on macOS. A screenshot tool puts the picture plus a link to its saved file on the clipboard, and macOS then also lists that file as a copied file. Treating that as a file copy attached nothing once the saved file was deleted (main attached the picture). A native clipboard probe showed the one reliable difference: a Finder copy also puts the file's name on the clipboard as text, a screenshot tool puts no text. So on macOS, a clipboard with files, no text and a picture pastes the picture, whether or not the saved file still exists; a Finder copy stays a file copy and never becomes its icon; a deleted Finder file shows the drop route's "Couldn't read the dropped files." Windows and Linux are unchanged.

Right-click Paste on macOS. That paste event hides the clipboard text, so Chat can only tell a Finder copy from a screenshot by reading the native clipboard, which takes a moment. Chat reserves one pending chip per copied file before that read, so Send is held from the first moment, and those same chips carry the files through the drop route (or, for a screenshot, the first one becomes the picture). Removing a chip during the read cancels that file; a chat view that closes during the read still gets its files in its draft.

One ordering change on the app-menu path: before it decides about a picture, Chat now waits for its clipboard text and file-list read, a single local call to the main process, so it can tell whether files were copied. A screenshot's chip therefore appears after that read, not alongside it. Text still never waits on the picture, which was the guarantee from #23784.

Why

The alternative was to keep "attach a picture" as the only paste-to-attach path and teach the macOS main process to read a copied image file into a picture, as it already does on Windows. That still loses non-image files, keeps two meanings of "paste a file", and attaches a re-encoded copy instead of the file. Treating a pasted file as a dropped file gives one rule for both gestures, and inherits the drop route's readability checks, paired-server upload and refusal notices without new code.

Differences from the common pattern

The common pattern treats every file on a paste as an attachment, taken from the paste event's files. This PR matches that for the paste event (Ctrl+V on Windows and Linux). Differences:

  • Intended: the app-menu paste (Cmd+V on macOS, right-click Paste) has no paste event in Orca, so the copied files' paths come from the main process's existing clipboard reader. Without this, macOS, where the bug was reported, would stay broken.
  • Intended: when copied files are on the clipboard, a picture next to them is ignored. On macOS a file manager's picture can be the file's icon, so attaching the file is the faithful result.
  • Intended: on macOS, Chat reads the native clipboard to tell a Finder copy from a screenshot tool's picture, because the paste event and the file list look the same for both; the native clipboard text is the only fact that differs.
  • Intended: pasted files go through Orca's drop route, so they get the same readability check, paired-server upload and refusal notice a dropped file gets. This is how Orca already handles files from outside; nothing new is added for paste.

Linked Issue

Linear STA-10652 (from a Discord report).

Visual Proof

Real hidden-Electron QA on macOS at 915cc102363 (clipboards written natively: a Finder copy carries the file link, the file list, the file name as text and a small icon; a screenshot tool's board carries a PNG and its saved file's link, no text). Earlier rounds compared main on the M4Air: main attached Finder's 64-pixel icon for a Finder copy, typed or dropped PDFs, and showed a broken preview for a screenshot whose saved file was deleted.

Case This PR
Finder copy of an existing PNG The real file attaches (its own path, real pixels); nothing typed; the agent received the same bytes
Finder copy of a deleted PNG No chip; "Couldn't read the dropped files."; never the icon
Screenshot tool board, saved file deleted The clipboard picture attaches as "Pasted image"
Right-click Paste with a slowed clipboard read A pending chip (spinner above the file name) appears at once with Send disabled, then settles as the same chip
Two copied files, PNG removed during the read The PNG never attaches; only the PDF does
Switch away and back during the read Files land once in the draft (Orca keeps the composer alive while away, so a true close-and-reopen is covered by the integration test, not live)
Finder copy, existing file Finder copy, deleted file Screenshot, saved file deleted
pr29089-A1-existing.png pr29089-A2-deleted.png pr29089-B2-screenshot-deleted.png
Pending during the read Settled Two files, PNG removed during the read Only the PDF attached
pr29089-E-pending.png pr29089-E-settled.png pr29089-F1-removed-pending.png pr29089-F1-pdf-only.png

After merging main (head ae2255e6b73). #29173 was squash-merged, so this PR now targets main with main merged in. Its diff against main is byte-identical to the reviewed head above. A smoke round on the merged build, on macOS (M4Air) in a hidden test copy with a throwaway home, passed:

  • A Finder copy attaches the real file.
  • A deleted Finder file shows "Couldn't read the dropped files."
  • A screenshot whose saved file was deleted attaches the picture.
  • A slowed right-click Paste shows a pending chip (spinner above the name, Send disabled), then settles as the same chip.
Finder copy Deleted Finder file Screenshot, saved file deleted Pending (slowed read) Settled
A1 A2 B2 E pending E settled

Testing

  • Updated use-native-chat-composer-paste.test.tsx: a Finder copy pasted from the app menu, and a pasted event whose files have local paths, both go to the drop route with nothing typed and no picture saved. The menu-paste tests that pinned the old "type the name" behavior were replaced.

  • macOS screenshot rule: a menu paste of files + no text + a picture pastes the picture on macOS and stays a file copy elsewhere.

  • Right-click Paste on macOS, through the real pending-chip cache and drop route: removing a chip before or after the clipboard read never attaches that file; closing and reopening the composer during the read lands the files in the original draft with the original chips (local and paired-server upload); disabling and re-enabling during the read never brings the paste back.

  • Unit tests and typecheck: CI (full PR workflow green on this head).

  • Live: hidden-Electron QA on macOS, above. Windows: an earlier round confirmed Explorer's Ctrl+V carries the real file paths; Windows code is unchanged since.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Cross-platform: macOS uses the app-menu path. Windows and Linux use the paste event, and the app-menu path for right-click Paste. Main's copied-file reader already covers all three (Windows reads a single file).
  • Remote: paired-server chats upload pasted files through the drop route's existing upload. Chats that can't take local files show the drop route's notice.
  • Web client: it has no getPathForFile and its readClipboardFilePaths returns nothing, so its paste behavior is unchanged.
  • Mobile: untouched.
  • Wire: no change.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…ttaches it

Copying an image file in Finder and pasting into Chat typed the file name
and attached nothing: the paste only attached a bitmap from the clipboard.
Copied files now go through the composer's drop route, on both the
app-menu paste and the paste event.

Fixes STA-10652
@brennanb2025

brennanb2025 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Copying an image file in Finder and pasting it into Chat could type its name or attach Finder’s small icon instead of the file. This PR sends copied files through the existing file attachment route, so pasting attaches the same original file as dropping it. It also preserves screenshot pixels when the clipboard includes the screenshot’s saved-file location, even after that saved file is deleted.

Review at b5d00604ad44823f40e126b07954255888ce28aa: not ready. Three independent source reviewers agree on one remaining ownership defect with two P2 effects in the new macOS paste-event classification:

  • Removing the pending attachment while the native clipboard read waits does not cancel the file paste: the file can reappear under a new attachment.
  • Closing the composer during that same read can discard the admitted file paste instead of keeping it with the draft.

The temporary pending attachment is replaced by a separately started file attachment after classification. Its removal and original draft lifetime do not govern that second attachment. Fixing is paused at the required design review; no change has been pushed by this review session. Earlier pending-admission, focus, composition, capability and late-completion repairs remain in place. The screenshot-with-missing-file regression is source-fixed by the new macOS rule. Unusual filename labels, alternate clipboard producers and inherited drop-specific error wording remain deferred concerns without new proven failures.

Verified: correctness, architecture and readiness/performance source reviews on the exact head; full PR workflow 38986389847, including static analysis, all five unit shards, relay, both package jobs and the successful required aggregate gate. No additional local tests, static checks or app launches were run in this review. Real final-head Electron QA and its screenshots are coordinator-owned and have not been supplied to this session; no app-level pass or ready certification is claimed.

For Brennan’s review

Final main-versus-branch screenshots remain outstanding. The places requiring attention are the macOS source decision and the pending-to-file handoff.

Update for head 915cc102363. The pending-to-file handoff above is fixed. A three-model design panel (with a final ruling) chose to keep one operation: on a macOS right-click Paste, Chat reserves one pending chip per copied file before reading the native clipboard, and those exact chips go through the existing drop route (local readability check or paired-server upload); a screenshot keeps the first chip as its picture. A blocking synchronous clipboard read was rejected because it would freeze the window for an unbounded time. A fresh correctness review of the two fix commits agrees both defects are fixed (removing a pending chip now cancels that file; a chat view that closes during the read still gets its files) and found no new P0-P2; the new integration tests use the real pending-chip cache and drop route. The head also merges #29173's latest commits (the pending chip now shows a spinner above the file name instead of a stacked kind icon).

Verified: the full PR workflow 38996477676 passed on this exact head. Real hidden-Electron QA on macOS at this head passed every case (results and screenshots in the PR description): Finder copies attach the real file, a deleted Finder file shows the drop route's notice and never the icon, a screenshot whose saved file is gone pastes its picture, the pending chip holds Send during a slowed clipboard read, and removing a chip during the read cancels that file. A true close-and-reopen of the composer could not be produced live (Orca keeps it alive while away), so that case rests on the integration test.

Update for head ae2255e6b73 (retargeted to main after #29173 merged). #29173 landed as one squashed commit, so merging main in produced conflicts in six files #29173 had touched. Main had not changed any of them since that squash, so the resolution keeps this branch's versions. The PR's diff against main is byte-identical to the reviewed head 915cc102363, so no new code needs review. The full PR workflow is green on this head. One unit-test shard was re-run after a test worker crashed with a segfault in an unrelated plugin test; every test passed in both attempts. A smoke QA round on the merged build passed on macOS (M4Air): a Finder copy, a deleted Finder file, a screenshot whose saved file was deleted, and a slowed right-click Paste. Screenshots are in the PR description, and the real agent config hashes were unchanged.

A local file drop checked each file before adding it, with nothing in the
composer meanwhile: Send during the check went without the file, which then
landed in the next draft; unmounting mid-check lost it; and files queued
behind an input-method composition were lost on unmount.

Drops now reserve a pending chip per file before the check, so Send waits
as it already does for server-upload drops. The prepared attachment travels
through the existing composition queue as one operation, settles into its
draft scope after unmount, releases its chips when refused or disabled, and
reads the scope's current image capability when it applies.
@brennanb2025
brennanb2025 changed the base branch from main to brennanb2025/chat-drop-reserves-pending October 15, 2026 15:32
…025/sta-10652-paste-copied-files

# Conflicts:
#	src/renderer/src/components/native-chat/native-chat-composer-paste-options.ts
#	src/renderer/src/components/native-chat/use-native-chat-composer-attachments.ts
…wn hook

Main's server-owned paste pushed the attachments hook past the file length limit.
…even beside its saved file

macOS lists a screenshot tool's saved file beside its picture, so the paste read as a file copy and attached nothing once the file was gone. Only a Finder copy also names its files as text.
…inder files from a screenshot

The paste event hides Finder's file names, so a path-backed picture reserves its chip at once and the native clipboard decides: named files go to the drop route, a screenshot's picture is saved from the clipboard. Both paste routes now share the clipboard-picture save.
…pending' into brennanb2025/sta-10652-paste-copied-files
@brennanb2025
brennanb2025 marked this pull request as ready for review October 17, 2026 00:12
…ures file

The paste tests outgrew the test file length limit.
…ame, not a stacked icon

A local drop now waits as a pending chip, which drew the kind icon, the file name and a centered spinner on top of each other. Without a thumbnail the chip shows the spinner above the name; with one, the dimmed picture keeps a spinner strip along its bottom.
…pending' into brennanb2025/sta-10652-paste-copied-files
…ipboard decides

A macOS paste of Finder files reserved one placeholder chip, asked the native
clipboard whether it held files or a screenshot picture, and then started a
separate attach for the files. Removing the placeholder did not cancel that
attach, and leaving the chat before the answer lost the paste.

The paste now reserves one chip per copied file before asking, and captures
the drop route's intake (owner, destination, chips) at the same moment. Files
continue those exact chips through the existing local check or server upload,
so removal cancels by the original chip and an unmounted composer still
finishes into its draft. A picture keeps the first chip and drops the rest.
Ordinary drops and the picker keep their fresh-admission checks.
…isable through the real intake

Drives the real pending cache, composer attachments, external intake and paste
hook with a delayed clipboard answer: removed chips never attach (before or
after the answer, through input-method composition), an unmount finishes both
the local and paired-server routes into the original draft with the original
chip IDs, and disabling the composer meanwhile does not bring the paste back.
@brennanb2025
brennanb2025 changed the base branch from brennanb2025/chat-drop-reserves-pending to main October 17, 2026 07:01
…2-paste-copied-files

# Conflicts:
#	src/renderer/src/components/native-chat/native-chat-external-attachments-destination.test.tsx
#	src/renderer/src/components/native-chat/native-chat-resolved-path-ownership.ts
#	src/renderer/src/components/native-chat/native-chat-session-attachment-drop.ts
#	src/renderer/src/components/native-chat/use-native-chat-composer-attachments.ts
#	src/renderer/src/components/native-chat/use-native-chat-external-attachments.ts
#	src/renderer/src/components/native-chat/use-native-chat-resolved-path-attachments.ts
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: bd7022d9-79a9-405b-9334-382974de8f7d
📥 Commits

Reviewing files that changed from the base of the PR and between ed3c3cc and ae2255e.

📒 Files selected for processing: 20
  • src/renderer/src/components/native-chat/NativeChatComposer.tsx
  • src/renderer/src/components/native-chat/native-chat-clipboard-image-save.ts
  • src/renderer/src/components/native-chat/native-chat-clipboard-payload.ts
  • src/renderer/src/components/native-chat/native-chat-composer-paste-options.ts
  • src/renderer/src/components/native-chat/native-chat-composer-paste-test-fixtures.tsx
  • src/renderer/src/components/native-chat/native-chat-composer-upload-completion.test.tsx
  • src/renderer/src/components/native-chat/native-chat-external-attachments-destination.test.tsx
  • src/renderer/src/components/native-chat/native-chat-remote-image-paste.test.tsx
  • src/renderer/src/components/native-chat/native-chat-resolved-path-ownership.ts
  • src/renderer/src/components/native-chat/native-chat-session-attachment-drop.ts
  • src/renderer/src/components/native-chat/use-native-chat-composer-attachments.ts
  • src/renderer/src/components/native-chat/use-native-chat-composer-paste-session-attachments.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-composer-paste.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-composer-paste.ts
  • src/renderer/src/components/native-chat/use-native-chat-external-attachments.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-external-attachments.ts
  • src/renderer/src/components/native-chat/use-native-chat-file-drops.ts
  • src/renderer/src/components/native-chat/use-native-chat-menu-paste-lifetime.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-paste-lifetime.ts
  • src/renderer/src/components/native-chat/use-native-chat-resolved-path-attachments.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.

📝 Walkthrough

The paste flow now detects copied file paths and routes them to attachment handling. It reserves attachment chips while resolving clipboard text and image renditions, with specific handling for macOS clipboard behavior. Attachment APIs also accept focus options. Tests cover copied files, image pastes, destination changes, and pending pastes during unmount or disablement.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to ae225

Pasting copied files into Chat now attaches them through the existing file-drop route. No concrete merge-blocking risk was identified in the supplied changes.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 31.03% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 29 functions across 20 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly and concisely describes the primary change: copied files attach in Chat like dropped files.
Description check Passed The description is detailed and mostly complete. It explains the user impact, implementation, rationale, linked issue, visual proof, testing, cross-platform behavior, remote behavior, and scope. Some …
Full details: Description check

Explanation

The description is detailed and mostly complete. It explains the user impact, implementation, rationale, linked issue, visual proof, testing, cross-platform behavior, remote behavior, and scope. Some checklist items remain unchecked, but the description reports that CI passed and provides sufficient review context.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@brennanb2025
brennanb2025 merged commit 0f9c12d into main Oct 17, 2026
53 of 55 checks passed
@brennanb2025
brennanb2025 deleted the brennanb2025/sta-10652-paste-copied-files branch October 17, 2026 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant