Repository navigation
fix: deliver team instructions larger than tmux's 16 KiB command limit - #24
Merged
Merged
Conversation
tmux sends each command to its server as one message of at most 16 KiB. tmuxPaste staged the paste buffer with `tmux set-buffer -b NAME TEXT`, so any payload above about 16,340 bytes failed with "command too long" before anything was pasted. Team messages may be 24 KiB, and a delegated task adds its envelope on top. injectText classified every tmux error on exact delegated input as uncertain. Dispatch therefore kept the claim while the worker sat at an empty prompt, and the reconciler failed the task five minutes later with "Delivery became uncertain". Coordinator follow-ups above the limit failed the same way. tmuxPaste now streams the payload to `tmux load-buffer -` on stdin. This has no size bound and keeps the payload out of argv, the process list and tmux error text; the old failure logged whole instructions into daemon.log. A failure while staging proves nothing reached the provider: - a delegated task now fails at once with its cause and no staged turn; - a coordinator message becomes a known non-delivery that is deferred for retry. A failure at paste or Enter stays uncertain and is never retried. Verified against the Claude Code 2.1.291 and Codex 0.159.2 TUIs in isolated scratch sessions. The new transport delivered prompts byte-exact from 8 KB to 256 KB on Claude and from 8 KB to 64 KB on Codex, with each hook or rollout entry about 1 s after the paste. No pane check before Enter is needed. Validated: - Full suite passes (557 tests, 9 new, including a real tmux regression on a private socket), plus shellcheck, .mjs syntax, npm run check and the Pi extension load. - Each of eight mutations turns the new tests red. - npm run audit fails on the newly published GHSA-jqcg-44mw-7w3h (proxy-addr); this change touches no dependencies. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Xs8AVBVrTGfpzUMiCQp2R
SergioTCG
force-pushed
the
fix/team-large-instruction-delivery
branch
from
October 6, 2026 11:13
72e7f4a to
e7f44d8
Compare
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
Team instructions and coordinator messages larger than about 16 KB never reached the worker. The task sat in
dispatchinguntil the reconciler failed it about five minutes later: "Delivery became uncertain before the provider acknowledged the delegated turn; SAB did not retry it to avoid duplicate work."The cause is a tmux command-size limit in the paste transport, not the provider TUI. SAB now streams the paste buffer to tmux on stdin, and a failure that provably happened before anything was pasted is reported at once instead of as "uncertain".
Root cause
tmux set-buffer -b NAME TEXTaccepts 16,340 bytes and rejects 16,400 withcommand too long.tmuxPastestaged every payload exactly that way, as a command argument.TEAM_MESSAGE_MAX_BYTESis 24 KiB, and the delegated-task envelope adds about 1.2 KB.injectTextclassified any tmux error on exact delegated input as uncertain.dispatchTeamTasklogged "provider input may have been accepted; refusing retry" and kept the claim.sab team message/continueto an active task) of that size failed the same way.execFileerror echoed the full argv, so the whole instruction was written intodaemon.log.Evidence from the failed batch (Claude team, five workers)
team task provider input may have been accepted; refusing retry … Command failed: tmux set-buffer -b sab-inject-… <sab-team-task …>followed bycommand too long.Fix
tmuxLoadBuffer(daemon/util.mjs) streams the payload totmux load-buffer -b NAME -on stdin.tmuxPastehas two outcomes:inputNotDelivered: true, because nothing was pasted or submitted.injectText, for exact delegated input:inputNotDelivered→ discard the staged generation, forget the injected text, and throw a definiteTeamError.dispatchTeamTasktherefore fails the task at once: "Provider injection failed: tmux could not stage the input: … Nothing reached the provider." There is still no second transport.injectCoordinatorTaskMessageOnce:inputNotDelivered→knownUndeliveredTeamMessage. That is the existing path for a provable pre-write rejection: it discards the staged generation and defers the message for retry.413 message_too_large, "Team messages may be at most 24576 bytes."), and a boundary test now covers it. Every size it accepts is now deliverable, so no lower cap is needed.Verified against real provider TUIs (isolated scratch sessions,
env -i, no bridge)The worktree's new
tmuxPastewas used for every check.UserPromptSubmitprompt (after<pasted_content>unwrapping); hook about 0.9 s after the pasteThe existing 300 ms pause before Enter is enough at every size, so verifying the paste before Enter was unnecessary. It would also need brittle, provider-specific pane parsing.
Alternatives considered
Duplicate-delivery safety
markTeamTaskRunningrejects a failed task, and the task stays failed.Tests
9 new tests;
test/tmux-input.test.mjswas updated for the stdin staging step.Transport and size boundaries:
TMUX_TMPDIRsocket rejects the maximum delegated task throughset-bufferwithcommand too longand stores it byte-exact throughtmuxLoadBuffer. The test is skipped when tmux is not installed.tmuxLoadBufferwrites the payload on stdin and surfaces tmux's stderr.message_too_large/ 413.Failure classification:
inputNotDeliveredand never pastes or presses Enter.dispatchTeamTask;Mutations. Each was applied on its own, then the files were restored byte-identical:
tmuxPastestages throughset-bufferargv againload-bufferputs the payload in argvinjectTextdrops the definite branchinjectTexttreats every failure as uncertain againValidation
npm test: 557 pass, 0 fail.npm run check,node --check(daemon/channel/scripts), Pi extension--list-modelsandshellcheck: all pass.npm run auditfails on the newly published GHSA-jqcg-44mw-7w3h.proxy-addr2.0.7 via@modelcontextprotocol/sdk → express, published aftermain's last green CI.Rollout notes
daemon/util.mjsanddaemon/daemon.mjs, both loaded at daemon start.mainand restarts the daemon, so merge only when you want to roll.launchctl kickstart -k gui/$(id -u)/si.sergej.claudeslackproxy.runningtask is released as failed;awaiting_releasetasks are kept;dispatchingclaim is never retried.So roll when no task is
dispatching, ideally between batches.🤖 Generated with Claude Code
https://claude.ai/code/session_011Xs8AVBVrTGfpzUMiCQp2R