fix(tmux): submit quick action prompts via Enter keycode after paste#267
Merged
Merged
Conversation
Quick action prompts (Merge PR, Address Changes, Fix Checks, Fix Conflicts) ended with `\n` so they would auto-submit in Claude Code's input. The Ghostty backend honored that contract by splitting on `\n` and emitting a synthetic Return keycode (36) after each segment — those events fire outside any bracketed-paste bracket, so the TUI saw them as real Enter keys. The tmux backend, however, shipped the entire string through `load-buffer` + `paste-buffer`. Claude Code's TUI enables bracketed-paste mode, so tmux wrapped the buffer in `\e[200~…\e[201~` and the trailing `\n` was delivered as literal text — typed as a newline character in the input field, never as Enter. Prompt visible, never submitted. Strip the trailing `\n` from the paste payload, then deliver a separate `Enter` via `tmux send-keys` *after* `paste-buffer` finishes (outside the bracket). Internal newlines are preserved as-is, matching Ghostty's per-line semantics. Empty-after-strip payloads skip the paste entirely since `load-buffer` rejects empty input on some tmux builds. Closes #264
dgershman
approved these changes
May 14, 2026
Collaborator
dgershman
left a comment
There was a problem hiding this comment.
Code & Security Review
Critical Issues
None.
Security Review
Strengths:
- No user-controlled data reaches shell execution —
sendKeyspasses["Enter"]as a literal tmux key name, not raw shell input. - Buffer names are derived from UUID, no injection vector via
tmux load-buffer/paste-buffer. - The existing
run()watchdog/timeout path is correctly reused for the newsendKeysmethod, so a hung tmux server can't block the main actor.
Concerns:
- None identified. The change stays within the existing trust boundary (Crow → tmux IPC over a private socket).
Code Quality
Well done:
- The doc comment on
sendText(TmuxBackend.swift:244-251) clearly explains the bracketed-paste root cause and references the Ghostty parity (keycode 36). Excellent for future maintainers. - Empty-payload guard (
if !payload.isEmpty) correctly avoids sending an emptyload-buffer, which some tmux builds reject — good defensive coding. sendKeysin TmuxController is a thin, general-purpose wrapper (send-keys -t <target> <keys...>) that will be reusable for future key-event needs (e.g.,C-c,Escape).- The
defer { ctrl.deleteBuffer(...) }stays inside theif !payload.isEmptyblock, so the buffer is only cleaned up when it was actually created.
Observations (non-blocking):
sendTextonly strips a single trailing\n(text.dropLast()). If a caller sends"hello\n\n", only one\nis stripped and the other is pasted as literal text inside the bracket. This matches the Ghostty backend's behavior (which splits on every\nand sends a keycode-36 for each), so multi-newline strings will behave differently between backends. Not a concern for the quick-action use case (always exactly one trailing\n), but worth noting ifsendTextis later used for multi-line submission. Current callers atregisterTerminal(line 183:command + "\n") andcrow sendall append a single\n, so this is fine today.- The existing test
sendTextRoundTripsThroughBuffer(TmuxBackendTests.swift:103) sends a payload without a trailing newline, so it exercises the paste-only path but not the newEnter-after-paste path. A test case likesendText(id: id, text: payload + "\n")would cover the new branch. Low risk given the simplicity, but would strengthen regression coverage.
Summary Table
| Priority | Issue |
|---|---|
| Green | Consider adding a test case for the trailing-newline + Enter path |
| Green | Document multi-newline behavior difference vs. Ghostty if sendText scope widens |
Recommendation: Approve — clean, focused fix that correctly addresses the bracketed-paste issue (#264). The approach mirrors what the Ghostty backend already does, the code is well-commented, and there are no security concerns.
3 tasks
dgershman
added a commit
that referenced
this pull request
May 15, 2026
…#274) Closes #272 ## Summary - Adds a 50ms delay between `paste-buffer` and `send-keys Enter` in `TmuxBackend.sendText()` to prevent a timing race where the Enter keystroke arrives before the TUI has finished processing the bracketed-paste bracket-end sequence - Only applies the delay when content was actually pasted (bare Enter-only sends skip the delay) - Adds two integration tests covering the trailing-newline paste+Enter path and the bare-Enter edge case ## Context PR #267 fixed quick-action prompts not submitting by splitting the trailing `\n` into a separate `send-keys Enter` after the paste. The same fix covers auto-respond prompts (same code path), but a timing race between the two tmux CLI invocations can cause the Enter to be silently dropped when the terminal has been idle — which is the typical state when auto-respond fires. ## Test plan - [ ] `swift build` compiles without errors - [ ] `swift test` passes (existing + new `TmuxBackendTests`) - [ ] Manual: create a tmux-backed session with a PR that has failing CI, verify the auto-respond prompt is submitted automatically 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Quick action prompts (Merge PR, Address Changes, Fix Checks, Fix Conflicts) end with a trailing `\n` so they auto-submit in Claude Code's input. The Ghostty backend honored that contract by splitting on `\n` and emitting a synthetic Return keycode (36) — those events fire outside any bracketed-paste bracket, so the TUI saw them as real Enter keys.
The tmux backend shipped the entire string through `tmux load-buffer` + `tmux paste-buffer`. Claude Code's TUI enables bracketed-paste mode, so tmux wrapped the buffer in `\e[200~ … \e[201~` and the trailing `\n` was delivered as literal text — typed as a newline character in the input field, never as Enter. Prompt visible, never submitted.
Changes
Ghostty path is untouched.
Test plan
Closes #264