Repository navigation
fix(tmux): submit text as a bracketed paste so Codex does not swallow Enter - #628
Conversation
… Enter sendInput() typed the text of a text+Enter pair with `send-keys -l` and sent Enter 50 ms later. Codex detects an unmarked paste from a fast character burst and folds an Enter inside that window into the paste as a newline, so the prompt stays in the composer unsubmitted. Measured with codex-cli 0.162.1 in a throwaway tmux pane: 1505 chars stuck 3/3 as `[Pasted Content 1506 chars]`, a 98-char line stuck 1/4. SubmitVerifier re-presses Enter for the short case, but it cannot match the long one: the composer shows the placeholder instead of the prompt's head. The text now goes in via set-buffer + `paste-buffer -p -d` (markers only when the pane requested bracketed paste), followed by the separate Enter as before. Same pane, same texts: 4/4 and 3/3 submitted. Each send gets its own named buffer; a paste that fails (pane or server gone) deletes it, since tmux never trims named buffers on its own. Also escapes a trailing `;` in the text, which tmux treats as a command separator (`send-keys -l 'x;'` typed `x`, a lone `;` typed nothing). sendInputToPane() has no callers in src/ and is left as it was. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Thanks @Randalix for this, and for the careful measurements in #627. The PR puts the text half of a text+Enter submit into the pane as one bracketed paste, so Codex can no longer fold the Enter into a paste burst. The mechanism checks out. tmux adds the markers only when the pane asked for them, the per-call buffer and the One thing needs a change before merge:
Two doc lines still describe the old path:
Optional, and fine as a follow-up: the Once the single-character guard and its test are in, this is ready to merge, and I will add the changeset at release. |
Claude Code's and Codex's selection dialogs ignore a bracketed paste, so after the previous commit a pasted `3` + Enter confirmed the highlighted option instead of option 3. sendInput() now types a single character with `send-keys -l` as master did (submitsAsKeys()). One character cannot form the burst Codex reads as a paste, so the Codex fix is unaffected. The trailing-`;` escape moves into escapeTmuxTrailingSemicolon() and now also covers every `send-keys -l` call in sendInput() and sendInputToPane(); tmux 3.5a types `x;` for `send-keys -l 'x\;'`. Docs: the @fileoverview lines and orchestrator-loop-architecture.md still described `send-keys -l`; CLAUDE.md, architecture-invariants.md and cron-guide.md name the single-character exception. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Thanks, that dialog case was a real hole. Pushed as a separate commit on top so the diff stays readable:
I couldn't repeat your menu repro here: on Claude Code 2.1.300 the "Teach auto mode" menu came up once and then not again in a fresh folder. The guard sends single characters down master's old path, which is the one you measured as selecting correctly. |
|
Merged and shipped in 1.42.0, closing #627. Thanks @Randalix! Every point from the review is in: single characters stay typed, the doc lines are fixed, and you took the optional trailing- Two small follow-ups if you feel like it, neither blocking: the |
Closes #627
sendInput()now puts the text of a text+Enter pair into the pane as one bracketed paste (set-buffer+paste-buffer -p -d) instead of typing it withsend-keys -l, then sends Enter 50 ms later as before. Codex no longer folds that Enter into a paste burst, so long prompts stop sitting unsubmitted in its composer. Measurements are in #627.What changed
buildPasteTextCommands(tmux, target, text)insrc/tmux-manager.tsreturns the three commands of one paste:setfills a buffer of its own (codeman-submit-<pid>-<seq>),pastepastes it bracketed into the target and deletes it, anddiscarddeletes it when the paste failed.pasteText(run, cmds)runs them. If the paste fails (pane or server gone), it deletes the buffer and rethrows the paste's error. tmux never trims named buffers by itself (buffer-limitonly applies to automatic ones), and on my fork one stayed behind on 2026-10-11 after a paste into a session that had just been deleted (can't find pane).;is sent as\;. tmux reads an argument that ends in;as a command separator: on mastersend-keys -l 'x;'typesxand a lone;types nothing; withset-buffera lone;fails outright (no data specified). Checked against tmux 3.5a forx;,;,x;;andx\;, all four come out ofshow-bufferunchanged.send-keys -lfor this path:writeViaMux(), the SubmitVerifier header, two comments in the input route,CLAUDE.md,docs/architecture-invariants.md,docs/cron-guide.md.What stays as it was
\ris still typed withsend-keys -l. There is no Enter to fold, and the text lands in the composer either way.sendInputToPane()has no callers insrc/, so I left it alone.paste-buffer -psends the plain text, the same bytessend-keys -lsent. I haven't measured an SSH remote case, where the paste goes into the local pane that runs ssh; if the markers don't get through there, it behaves like master./costin Claude Code and/statusin Codex both executed.[Pasted text #1]whether it was typed or pasted, and Codex shows[Pasted Content N chars], so the verifier can't match a long prompt in either CLI on master or with this change. I noted it in thebuildPasteTextCommandsdoc comment and did not touch the verifier.Tests
test/tmux-submit-paste.test.ts(18 tests):send-keysin the paste commandstmuxthat prints its argv: quotes,$(), tmux format strings, a leading-, non-ASCII, 1500 chars, the trailing-;casespasteText(): a failed paste deletes the buffer and reports the paste's error, also when the delete fails too; a failedset-bufferstops before the pastesendInput()'s source, sincesendInput()returns before touching tmux under vitest; it fails on master'ssend-keys -lFull
npm test: 11276 passed, 12 skipped, 0 failed (568 files passed, 1 skipped). The first run had one red,frontend-public-tooling>check:public-assets, because this worktree had never run postinstall and had no spreadsheet vendor assets; afternode scripts/prepare-spreadsheet-assets.mjsthat file passes too. tsc, eslint on the changed files, prettier andcheck:frontend-syntaxare green.Live
Throwaway Codex pane (codex-cli 0.162.1) on its own tmux socket, see #627: 98 chars 4/4 and 1505 chars 3/3 submitted with the paste, against 3/4 and 0/3 on master.
Through the input route:
codeman agent sendof a 1506-char prompt to a Codex worker on my fork's server, three times. The rollout has three user messages of exactly 1506 characters and threetask_complete. That server runs the same paste commands, but without thediscardbranch, which is new in this PR and covered by the unit tests above.No changeset.
🤖 Generated with Claude Code