fix: paste into Claude on Windows; route clipboard paste through provider-aware injection#2180
Conversation
…ction#1901) Pasting into Claude failed on Windows because Ctrl+V was not recognized as a paste shortcut in the terminal pane, and the existing Ctrl+Shift+V path wrote clipboard text directly to the PTY without bracketed-paste formatting — so multiline pastes were submitted line-by-line. Funnel all clipboard pastes (keyboard shortcut and appPasteChannel) through pastePromptInjection so the existing provider-aware rule applies: Claude receives raw text, other providers receive bracketed paste for multiline content. Also recognize bare Ctrl+V as paste on Windows; Linux keeps Ctrl+Shift+V (Ctrl+V remains readline quoted-insert), macOS keeps its Cmd+V menu accelerator.
Greptile SummaryThis PR fixes clipboard paste on Windows (
Confidence Score: 3/5The Windows Ctrl+V fix and provider-aware multiline paste are correct, but routing all clipboard paste through buildPromptInjectionPayload introduces an unacknowledged change: every paste — including to plain shells — now has its text trimmed, stripping trailing newlines and leading indentation from the first line. The core keybinding change and providerId threading are sound. The undocumented trimming behavior is a real regression for shell use: pasting a command with a trailing newline previously auto-executed it; now it silently drops the newline and waits for manual Enter. Leading indentation on the first pasted line is also silently lost, which affects Python REPLs and similar indentation-sensitive environments. The Windows manual verification is also still outstanding per the checklist. src/renderer/lib/pty/use-pty.ts — the new pasteFromClipboard path trims all clipboard content via buildPromptInjectionPayload, which changes observed paste behavior for plain terminal sessions.
|
| Filename | Overview |
|---|---|
| src/renderer/lib/pty/use-pty.ts | Routes clipboard paste through pastePromptInjection and adds IS_WINDOWS_PLATFORM; the new path silently trims all pasted text, changing single-line paste semantics for plain shells (trailing newlines, leading indentation lost). |
| src/renderer/lib/pty/pty-keybindings.ts | Adds isWindowsPlatform param to shouldPasteToTerminal; Ctrl+V now triggers paste on Windows while Linux/macOS keep existing behavior. Logic is correct. |
| src/renderer/lib/pty/pty-pane.tsx | Threads providerId prop through to usePty; straightforward plumbing change with no issues. |
| src/renderer/features/tasks/conversations/conversations-panel.tsx | Passes activeConversation?.data.providerId to PtyPane; optional chaining correctly handles undefined for plain shells. |
| src/renderer/tests/terminalKeybindings.test.ts | Replaces Linux-only paste test with a full Mac/Windows/Linux matrix; all platform branches are exercised correctly. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User paste gesture] --> B{Platform?}
B -->|Windows: Ctrl+V or Ctrl+Shift+V| C[shouldPasteToTerminal → true]
B -->|Linux: Ctrl+Shift+V| C
B -->|macOS: Cmd+V via menu| D[appPasteChannel event]
C --> E[pasteFromClipboard]
D --> E
E --> F[navigator.clipboard.readText]
F --> G{text empty?}
G -->|yes| H[return]
G -->|no| I[pastePromptInjection]
I --> J[buildPromptInjectionPayload]
J --> K{providerId === 'claude'?}
K -->|yes| L[return trimmed text]
K -->|no, single-line| L
K -->|no, multi-line| M[return bracketed paste with trimmed text]
L --> N[sendInput to PTY]
M --> N
Comments Outside Diff (1)
-
src/renderer/lib/pty/use-pty.ts, line 309-323 (link)Clipboard text trimmed silently for all paste targets
buildPromptInjectionPayloadalways callstext.trim()before building the payload. For plain shell sessions (providerIdisundefined) with a single-line paste this changes the observable behavior: before,sendInput(text)sent the raw string; now leading/trailing whitespace (including a trailing\n) is stripped. Concretely, copyingls -la\nfrom a browser and pasting into a shell terminal used to auto-execute the command; after this change it lands without the newline and the user must press Enter manually. The trimming also silently drops leading indentation on the very first line of a multi-line paste, which can corrupt code pasted into a Python REPL or similar. The trim made sense for the prompt-injection context (where it was already applied to drag-and-drop), but applying it to arbitrary terminal paste changes semantics that users likely rely on.Prompt To Fix With AI
This is a comment left during a code review. Path: src/renderer/lib/pty/use-pty.ts Line: 309-323 Comment: **Clipboard text trimmed silently for all paste targets** `buildPromptInjectionPayload` always calls `text.trim()` before building the payload. For plain shell sessions (`providerId` is `undefined`) with a single-line paste this changes the observable behavior: before, `sendInput(text)` sent the raw string; now leading/trailing whitespace (including a trailing `\n`) is stripped. Concretely, copying `ls -la\n` from a browser and pasting into a shell terminal used to auto-execute the command; after this change it lands without the newline and the user must press Enter manually. The trimming also silently drops leading indentation on the very first line of a multi-line paste, which can corrupt code pasted into a Python REPL or similar. The trim made sense for the prompt-injection context (where it was already applied to drag-and-drop), but applying it to arbitrary terminal paste changes semantics that users likely rely on. How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
src/renderer/lib/pty/use-pty.ts:309-323
**Clipboard text trimmed silently for all paste targets**
`buildPromptInjectionPayload` always calls `text.trim()` before building the payload. For plain shell sessions (`providerId` is `undefined`) with a single-line paste this changes the observable behavior: before, `sendInput(text)` sent the raw string; now leading/trailing whitespace (including a trailing `\n`) is stripped. Concretely, copying `ls -la\n` from a browser and pasting into a shell terminal used to auto-execute the command; after this change it lands without the newline and the user must press Enter manually. The trimming also silently drops leading indentation on the very first line of a multi-line paste, which can corrupt code pasted into a Python REPL or similar. The trim made sense for the prompt-injection context (where it was already applied to drag-and-drop), but applying it to arbitrary terminal paste changes semantics that users likely rely on.
Reviews (1): Last reviewed commit: "fix: route clipboard paste through provi..." | Re-trigger Greptile
Remove WHAT-style narration that restated function names, a duplicate JSDoc on PtyPane.providerId (already documented on UsePtyOptions), and a tuple-shape comment in the keybinding test that the named locals already convey.
Use the strict AgentProviderId union for providerId on UsePtyOptions and PtyPane Props so typos cannot reach the paste-format check. Replace the discarded promise in pasteFromClipboard with a log.warn so clipboard or IPC failures are observable, matching the existing useTerminal logger pattern. Tighten one over-explained test comment.
Greptile review caught two regressions from routing clipboard paste through buildPromptInjectionPayload: leading indentation was stripped from the first line of code pastes (Python REPL, etc.), and shell commands ending in a newline (e.g. 'ls -la\n' copied from a browser) no longer auto-executed because the trim removed the newline and the multiline branch wrapped them in bracketed paste. Drop the unconditional trim — no caller actually depended on it — and treat a single trailing newline as "execute" rather than as multiline content that needs bracketed paste. Internal newlines still trigger bracketed paste for non-Claude providers, which is what prevents line-by-line submission in CLI agents like Codex. Add regression tests for both cases.
|
Thanks for the catch on the trim regression. Fixed in 7d50ae2:
The Python REPL / indented multiline case is still bracket-wrapped on non-Claude providers, which is the correct path — bracketed paste preserves the leading whitespace as bytes inside the wrap sequence. |
…1901) The Claude exemption in buildPromptInjectionPayload was added by commit 20610a1 ("fix(opencode): submit initial prompt reliably") for the keystroke-injection path that types the initial task prompt into the agent's TUI at session start, where Claude apparently mishandled the bracketed sequences. Routing user-driven clipboard paste through the same helper inherited that exemption — but Robin Mennens' issue generalaction#1901 evidence (Shift+Insert works, Ctrl+Shift+V does not) shows that Claude does handle bracketed paste correctly for ordinary user pastes. The exemption is wrong for that path. Add a required PromptInjectionMode discriminator so the Claude rule only applies to mode: 'initial-prompt'. Renderer's pastePromptInjection hardcodes mode: 'paste' so all three of its callers (clipboard paste, drag-drop, context bar) now route Claude through bracketed paste for multiline content. Add a regression test using the Lorem Ipsum scenario from the issue.
|
Following up on issue #1901's full comment thread — Robin Mennens reported a second case I hadn't fully addressed: multi-paragraph pastes (Lorem Ipsum example) breaking when pasted into Claude via Ctrl+Shift+V, while Shift+Insert worked. Root cause: the `providerId !== 'claude'` exemption in `buildPromptInjectionPayload` was added by 20610a1 ("fix(opencode): submit initial prompt reliably") for keystroke injection of the initial task prompt at session start, where Claude's TUI apparently mishandled bracketed paste. Routing user-driven clipboard paste through the same helper inherited that exemption — but Robin's evidence (Shift+Insert works, which routes through xterm.js's bracketed paste) shows Claude does handle bracketed paste fine for ordinary clipboard pastes. Fixed in b41adee by adding a `PromptInjectionMode = 'paste' | 'initial-prompt'` discriminator. The Claude exemption now applies only to `mode: 'initial-prompt'`. The renderer's `pastePromptInjection` wrapper hardcodes `mode: 'paste'` so all three of its callers (clipboard, drag-drop, context bar) route Claude multiline content through bracketed paste. Added a regression test that mirrors the Lorem Ipsum scenario from the issue. Issue thread coverage:
|
Summary
Fixes #1901 — pasting into Claude does not work on Windows.
There were two overlapping bugs in the terminal paste pipeline:
Ctrl+V on Windows did nothing. The renderer only recognized
Ctrl+Shift+V(Linux convention) as a terminal paste shortcut. On Windows, users naturally pressCtrl+V, which fell through to xterm.js's default and never reached our paste handler.Multiline pastes broke for non-Claude providers.
pasteFromClipboardwrote raw text to the PTY (sendInput(text)) with no provider-aware formatting. For agents that expect bracketed paste, every newline was interpreted as Enter — so the first line submitted the prompt before the rest of the text arrived. This is the "Ctrl+Shift+V seems broken at times" symptom in the issue thread.The codebase already had a provider-aware paste function (
pastePromptInjectioninsrc/shared/prompt-injection.ts) that correctly sends raw text to Claude and bracketed paste to others. It was wired into drag-and-drop and the context bar, but not into the clipboard-paste shortcut.This PR:
pasteFromClipboardthroughpastePromptInjectionso the keyboard shortcut and theappPasteChannellistener share the same provider-aware formatting.providerIdfrom the conversation throughPtyPane→usePty(via a ref so swapping agents mid-session does not remount xterm.js).Ctrl+Vas a terminal paste shortcut on Windows. Linux keepsCtrl+Shift+V(soCtrl+Vremains readlinequoted-insert); macOS keeps itsCmd+Vmenu accelerator.Files changed
src/renderer/lib/pty/pty-keybindings.ts—shouldPasteToTerminalnow takesisWindowsPlatform.src/renderer/lib/pty/use-pty.ts— acceptsproviderId, paste callback usespastePromptInjection, addsIS_WINDOWS_PLATFORM.src/renderer/lib/pty/pty-pane.tsx— forwardsproviderIdtousePty.src/renderer/features/tasks/conversations/conversations-panel.tsx— passesactiveConversation?.data.providerId. Plain shell terminals leave it undefined, so they keep the existing bracketed-paste-for-multiline behavior, which is correct for a shell.src/renderer/tests/terminalKeybindings.test.ts— replaces the Linux-only test with a Mac/Windows/Linux matrix.Test plan
pnpm run formatpnpm run lintpnpm run typecheckpnpm test(823/823 passed, including new platform-matrix cases)quoted-insert.The logic and unit tests pass on macOS; I was not able to verify the end-to-end Windows flow locally.
Closes #1901