Skip to content

fix: bracket multi-line terminal pastes so pasted code isn't treated as Enter - #1013

Open
SmolSmolStar wants to merge 2 commits into
web3dev1337:mainfrom
SmolSmolStar:fix/bracketed-paste
Open

fix: bracket multi-line terminal pastes so pasted code isn't treated as Enter#1013
SmolSmolStar wants to merge 2 commits into
web3dev1337:mainfrom
SmolSmolStar:fix/bracketed-paste

Conversation

@SmolSmolStar

Copy link
Copy Markdown
Contributor

Summary

Pasting multi-line code into a terminal (e.g. a SQL block or a snippet from chat) misbehaves: the paste is sent to the PTY as a raw string, so every newline is read as an Enter keypress. The result is code that submits line-by-line or early instead of landing as one clean block. This affects both the worktree terminals and the Commander terminal.

Root cause

The paste handlers send the clipboard text straight through (sendTerminalInput / sendInput) with no bracketed-paste wrapping. Bracketed paste (ESC[200~ESC[201~) is the mechanism that tells a program "this is pasted text — treat the newlines as literal input, don't act on them." Without it, multi-line pastes are indistinguishable from a burst of typed Enters.

Change

  • client/terminal.js (worktree terminals): the three raw-send sites now route through a new pasteTextToTerminal() helper that calls xterm's own terminal.paste(). That normalizes newlines and, only when the running program has bracketed-paste mode enabled (it sent ESC[?2004h), wraps the text in ESC[200~ … ESC[201~. It flows through terminal.onData (which already forwards to sendTerminalInput and marks the session active), and updateInputBuffer ignores ESC-prefixed data so autosuggestions aren't polluted. Falls back to a raw send if the xterm instance is unavailable. Image paste is untouched.
  • client/commander-panel.js (Commander terminal): its paste handler intentionally bypasses xterm.onData (to keep pastes out of slash-command capture), so it brackets inline via a small bracketPastedText() helper that reads terminal.modes.bracketedPasteMode.
  • No regression for plain programs: anything that doesn't enable bracketed-paste mode receives the text unwrapped, exactly as before.

Test plan

  • Extends tests/e2e/commander-paste.spec.js with a multi-line case: with bracketed-paste mode enabled, the POST /api/commander/input body must be ESC[200~line one\rline two ESC[201~. The existing single-line / mode-off case still asserts a raw body (guards against regressions).
  • The underlying mechanism (xterm terminal.paste() bracketing + terminal.modes.bracketedPasteMode reflecting ESC[?2004h) was verified in a headless browser against xterm 5.3.0.
  • Note: I could not execute the e2e suite locally on Windows (the repo's e2e webServer command uses POSIX VAR=value env-prefix syntax that cmd.exe rejects, and the run-e2e-safe.js runner spawns npx which doesn't resolve under Git Bash). The tests are discovered by playwright --list and will run in CI on Linux.

🤖 Generated with Claude Code

SmolSmolStar and others added 2 commits July 12, 2026 22:40
…as Enter

Pasting multi-line text into a terminal sent the raw string (newlines included)
straight to the PTY. With no bracketed-paste wrapping, each newline is read as an
Enter keypress, so multi-line code submits line-by-line / early instead of landing
as one block. This hit both the worktree terminals and the Commander terminal.

- client/terminal.js: route paste through a new pasteTextToTerminal() helper that
  uses xterm's terminal.paste(). paste() normalizes newlines and, when the running
  program has bracketed-paste mode enabled (it sent ESC[?2004h), wraps the text in
  ESC[200~ .. ESC[201~ so it's treated as literal pasted input. It flows through
  terminal.onData (already forwarding to sendTerminalInput), and updateInputBuffer
  ignores ESC-prefixed data so autosuggestions aren't polluted. Falls back to a raw
  send if the xterm instance is unavailable. Image paste is unchanged.
- client/commander-panel.js: the Commander paste handler bypasses xterm.onData (to
  keep paste out of slash-command capture), so it brackets inline via a small
  bracketPastedText() helper using terminal.modes.bracketedPasteMode.
- Programs that don't enable bracketed-paste mode still receive the text unwrapped,
  so nothing regresses for them.

Tests: extend tests/e2e/commander-paste.spec.js with a multi-line case asserting the
POST body is bracketed when mode is on (the existing single-line/mode-off case still
asserts a raw body). The bracketing mechanism (xterm.paste + terminal.modes) was
verified in a headless browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rminator

Review feedback: routing pastes through xterm's paste() meant unbracketed
multi-line pastes now hit the autosuggestion input-buffer tracker and got
stored whole as the "current line", later polluting command history with a
glued bogus entry (bracketed pastes dodged this only via their ESC prefix).
paste() fires onData synchronously, so a flag around the call lets the
onData handler skip tracking for the burst. Also strips an embedded
ESC[201~ from Commander pastes so crafted clipboard content can't close the
bracket early, and documents the fail-open raw-text fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@web3dev1337

Copy link
Copy Markdown
Owner

Reviewed as part of an open-PR sweep — the mechanism was verified against the real xterm 5.3.0 source (modes.bracketedPasteMode is a stable API; paste() normalizes and brackets exactly as this PR assumes) and the orchestrator's server-side two-write submit path is confirmed untouched. One real regression found and fixed: unbracketed multi-line pastes now flowed through onData into the autosuggestion input-buffer tracker and were stored whole as the current line, later polluting command history. Pushed a fix that flags paste-originated bursts (paste() fires onData synchronously) so tracking skips them, plus ESC[201~ terminator-injection hardening for the Commander's hand-rolled wrapper and a comment on the deliberate fail-open fallback. Verdict after fix: good to merge.

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.

2 participants