Skip to content

fix(interactive): hand tmux a script path, never the system prompt (#96) - #97

Merged
Lexus2016 merged 1 commit into
mainfrom
fix/96-tmux-command-too-long
Sep 11, 2026
Merged

Lexus2016 merged 1 commit into
mainfrom
fix/96-tmux-command-too-long

Conversation

@Lexus2016

Copy link
Copy Markdown
Owner

Closes #96.

Root cause

A tmux new-session command travels over tmux's imsg socket, which caps the whole message at ~16 KB. Reproduced on tmux 3.7c (macOS): 16 300 B starts, 16 340 B → failed to send command, 20 000 B → command too long. runInteractiveSingle() put the entire --append-system-prompt (studio prompt + skills + AGENTS.md, up to 64 KB) inside that command, and spawned tmux with stdio:'ignore', so the only thing on screen was the generic "failed to start tmux session".

Fix (claude-interactive.js)

  • tmuxLaunchCommand() writes the full invocation into a content-addressed script inside a per-process mkdtemp (0700) directory and hands tmux sh <path> — ~60 bytes regardless of prompt size. The script execs, so the pane process is still claude.
  • Script rather than "$(cat file)" (the suggestion in the issue): tmux runs the command through default-shell, the user's login shell — $( ) is a syntax error under csh/tcsh and strips trailing newlines elsewhere. sh <path> is plain words in every shell.
  • Private dir, not a predictable /tmp name: this file is executed, so a pre-created file by another local user would be run instead of ours.
  • Unwritable tmpdir → falls back to the pre-Bug: Interactive engine fails to start for any project with an AGENTS.md larger than ~16 KB (tmux "command too long") #96 inline command (works below 16 KB as before).
  • tmux stderr is now piped and appended to the error frame, so command too long reaches the user.
  • buildInteractiveCommand() extracted (pure) so the argv shape is unit-testable.
  • agents-md.js: MAX_BYTES comment corrected — it is an argv budget (macOS ARG_MAX is 1 MB, not 256 KB); the tmux ceiling no longer applies.

Test

test/engine-spawn-cmd.test.js (wired into npm test): 15 pure checks + an end-to-end run through a real tmux on a private socket with a fake claude that dumps its argv — asserts the 40 KB prompt (with non-ASCII) arrives byte-identical. The pre-fix inline form runs as an informational control and prints command too long.

$ node test/engine-spawn-cmd.test.js
  info inline command (40224 B) → command too long
  ok   tmux accepted the launch command
  ok   child received the 40 KB prompt byte-identical
21 passed, 0 failed

$ npm test   → EXIT=0 (79 files, 59 plain-node suites "0 failed")

Cost / risk

  • One small script file per distinct config per process, in a 0700 tmp dir; not cleaned up (same lifecycle as the existing ccs-mcp-*.json). Content-addressed, so a respawn with the same config reuses it.
  • The pane's start command is now sh /tmp/ccs-spawn-…/spawn-<hash>.sh instead of the readable claude … line; ps still shows the exec'd claude argv.
  • Does not touch the API engine or SSH paths — they never went through tmux.

🤖 Generated with Claude Code

A tmux `new-session` command travels over its imsg socket, capped at ~16 KB
(measured on tmux 3.7c: 16 300 B starts, 16 340 B "failed to send command",
20 000 B "command too long"). The subscription engine put the whole system
prompt inside that command, so a project with a 25 KB AGENTS.md — legal,
agents-md.MAX_BYTES is 64 KB — could never start an interactive session, and
the real tmux error was discarded with stderr.

- tmuxLaunchCommand() writes the invocation to a content-addressed script in
  a per-process 0700 mkdtemp dir and hands tmux `sh <path>` (~60 bytes).
  A script rather than `"$(cat file)"`: tmux runs the command through the
  user's login shell, where `$( )` is a csh/tcsh syntax error. The script
  execs, so the pane process is still `claude`. Falls back to the inline
  command if the temp dir is unwritable.
- tmux stderr is piped and appended to the `error` frame.
- buildInteractiveCommand() extracted so the argv shape is unit-testable.
- test/engine-spawn-cmd.test.js starts a REAL tmux session with a 40 KB
  prompt and asserts the child's argv byte-identical; the pre-fix inline
  form is run as an informational control (prints `command too long`).
- agents-md.js: MAX_BYTES comment corrected (it is an argv budget; macOS
  ARG_MAX is 1 MB, not 256 KB).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Lexus2016
Lexus2016 merged commit a2d0ddf into main Sep 11, 2026
2 checks passed
@Lexus2016
Lexus2016 deleted the fix/96-tmux-command-too-long branch September 11, 2026 16:07
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: Interactive engine fails to start for any project with an AGENTS.md larger than ~16 KB (tmux "command too long")

1 participant