Skip to content

fix: paste into Claude on Windows; route clipboard paste through provider-aware injection#2180

Open
Aarekaz wants to merge 5 commits into
generalaction:mainfrom
Aarekaz:emdash/fix-cannot-paste-text-claude-kqg3n
Open

fix: paste into Claude on Windows; route clipboard paste through provider-aware injection#2180
Aarekaz wants to merge 5 commits into
generalaction:mainfrom
Aarekaz:emdash/fix-cannot-paste-text-claude-kqg3n

Conversation

@Aarekaz
Copy link
Copy Markdown

@Aarekaz Aarekaz commented May 22, 2026

Summary

Fixes #1901 — pasting into Claude does not work on Windows.

There were two overlapping bugs in the terminal paste pipeline:

  1. Ctrl+V on Windows did nothing. The renderer only recognized Ctrl+Shift+V (Linux convention) as a terminal paste shortcut. On Windows, users naturally press Ctrl+V, which fell through to xterm.js's default and never reached our paste handler.

  2. Multiline pastes broke for non-Claude providers. pasteFromClipboard wrote 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 (pastePromptInjection in src/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:

  • Routes pasteFromClipboard through pastePromptInjection so the keyboard shortcut and the appPasteChannel listener share the same provider-aware formatting.
  • Threads providerId from the conversation through PtyPaneusePty (via a ref so swapping agents mid-session does not remount xterm.js).
  • Adds Ctrl+V as a terminal paste shortcut on Windows. Linux keeps Ctrl+Shift+V (so Ctrl+V remains readline quoted-insert); macOS keeps its Cmd+V menu accelerator.
  • Updates the keybinding tests with a per-platform matrix.

Files changed

  • src/renderer/lib/pty/pty-keybindings.tsshouldPasteToTerminal now takes isWindowsPlatform.
  • src/renderer/lib/pty/use-pty.ts — accepts providerId, paste callback uses pastePromptInjection, adds IS_WINDOWS_PLATFORM.
  • src/renderer/lib/pty/pty-pane.tsx — forwards providerId to usePty.
  • src/renderer/features/tasks/conversations/conversations-panel.tsx — passes activeConversation?.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 format
  • pnpm run lint
  • pnpm run typecheck
  • pnpm test (823/823 passed, including new platform-matrix cases)
  • Manual verification on Windows 11: Ctrl+V pastes single- and multi-line text into Claude.
  • Manual verification on Linux: Ctrl+Shift+V still pastes; bare Ctrl+V still reaches the PTY as quoted-insert.
  • Manual verification on macOS: Cmd+V still pastes via the Edit menu; nothing regresses.

The logic and unit tests pass on macOS; I was not able to verify the end-to-end Windows flow locally.

Closes #1901

…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-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR fixes clipboard paste on Windows (Ctrl+V) and corrects multiline paste formatting for non-Claude providers by routing all paste paths through the existing pastePromptInjection/buildPromptInjectionPayload pipeline. It threads providerId from the conversation down through PtyPane and usePty via a ref so agent-switching mid-session does not remount the terminal.

  • Windows paste: shouldPasteToTerminal now accepts isWindowsPlatform and returns true for bare Ctrl+V on Windows, while Linux keeps Ctrl+Shift+V and macOS routes through the Electron menu accelerator.
  • Provider-aware formatting: pasteFromClipboard now calls pastePromptInjection, sending raw text to Claude and bracketed-paste sequences to all other providers, fixing the "first line submits before rest arrives" bug for multiline pastes.
  • Behavioral side-effect: buildPromptInjectionPayload always trims the pasted text. Single-line pastes to plain shells (no providerId) are now trimmed, which strips trailing newlines (breaking the paste-to-auto-execute workflow) and leading whitespace on the first line of multiline pastes.

Confidence Score: 3/5

The 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.

Important Files Changed

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
Loading

Comments Outside Diff (1)

  1. src/renderer/lib/pty/use-pty.ts, line 309-323 (link)

    P1 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.

    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

Aarekaz added 3 commits May 22, 2026 01:45
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.
@Aarekaz
Copy link
Copy Markdown
Author

Aarekaz commented May 22, 2026

Thanks for the catch on the trim regression. Fixed in 7d50ae2:

  • Dropped the unconditional text.trim() in buildPromptInjectionPayload — verified no caller actually depended on it (drag-drop joins single-quoted paths with spaces, no leading/trailing whitespace; the prompt-injection path didn't need it either).
  • Changed the bracketed-paste heuristic from "any newline = multiline" to "internal newlines = multiline." A single trailing newline now means "execute this line" and passes through raw, so ls -la\n pasted into a shell auto-executes again.
  • Added two regression tests covering both cases (trailing-newline auto-execute and leading-whitespace preservation on multiline pastes).

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.
@Aarekaz
Copy link
Copy Markdown
Author

Aarekaz commented May 22, 2026

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:

  • ✅ Ctrl+V on Windows
  • ✅ Multiline pastes into non-Claude providers
  • ✅ Multiline pastes into Claude (this commit)
  • 🛑 In-app paste hint / docs (separate UX work, out of scope)

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.

[bug]: cannot paste text into Claude

1 participant