From tmux
Read and drive other tmux panes when Claude runs inside tmux. Use whenever the user points at something running in another pane/split/window — their dev server, test watcher, build, logs, REPL, or another shell — e.g. "errors in my dev server", "read/tail my other pane", "is my server up and on what port", "run this in my other split", "restart the watcher". Also for driving interactive/long-running CLIs (Node REPL, vite/next dev, vitest, node inspect) and spawning isolated background tmux sessions.
How this skill is triggered — by the user, by Claude, or both
Slash command
/tmux:tmuxThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use tmux as a programmable terminal multiplexer — both to **drive the panes the user is already watching** (their dev server, test watcher, REPL, logs) and to **run your own isolated sessions**. Works on Linux and macOS with stock tmux.
Use tmux as a programmable terminal multiplexer — both to drive the panes the user is already watching (their dev server, test watcher, REPL, logs) and to run your own isolated sessions. Works on Linux and macOS with stock tmux.
If you were launched from inside tmux, a SessionStart hook will already have told you so and listed the visible panes — which means Mode A below is available. When in doubt, prefer acting on the surfaces the user can actually see; that's usually what they mean.
This is the high-value mode when you're running inside the user's tmux: you can read and drive the same panes they're looking at, instead of spinning up a sandbox they can't see.
Confirm you're inside their tmux and find the server ($TMUX is socket,pid,session; the socket is the first field):
[ -n "$TMUX" ] && echo "in tmux — socket=${TMUX%%,*} pane=$TMUX_PANE"
Plain tmux ... already targets that server; tmux -S "${TMUX%%,*}" ... is the explicit form. The user usually works one window per project, so the panes sharing your window are the relevant ones. The bundled scripts cover the common operations — prefer them over hand-rolling:
./scripts/find-panes.sh -p # panes in YOUR window + listening ports
./scripts/find-panes.sh --session -p # widen to the whole session
Never assume :0.0. The user's tmux may set base-index 1 / pane-base-index 1, so targets can be 1-based. Always use the exact session:win.pane reported by find-panes.sh (or the SessionStart hook, or $TMUX_PANE for your own pane).
Read a pane's output — safe, do it freely:
tmux capture-pane -p -J -t projects:1.2 -S -200 # scrape a dev server's recent log
Run a one-off in the user's shell pane and get just its output back (waits for completion, prompt-agnostic, exits with the command's code):
./scripts/run-in-pane.sh -t projects:1.3 -- pnpm vitest run src/auth
Send keystrokes into a pane through the guard, which refuses editors/pagers (read the safety rules below first):
./scripts/safe-send.sh -t projects:1.2 --enter -- 'rs' # type 'rs' + Enter (e.g. nodemon restart)
./scripts/safe-send.sh -t projects:1.2 --keys -- C-c # control keys
Reading with capture-pane never disturbs anything — reach for it first and often. send-keys is different: the keystrokes land in whatever currently has focus in that pane, so a careless send can corrupt the user's editor or kill a process they care about. Before any send-keys into a pane you didn't create:
[command] from the list above). Don't send keystrokes into an editor (nvim, vim, emacs), a pager (less, man), or any stateful interactive program unless the user explicitly asked you to drive it.tmux new-window -n claude-task (or a fresh session), then drive that. They still see it; you don't clobber their work.-l for literal text; send control keys (C-c, Enter) as separate calls. Then capture-pane to confirm the effect before moving on.capture-pane/attach command to verify.safe-send.sh enforces the editor/pager check for you, and run-in-pane.sh is the safe way to run a one-off in a shell pane and capture its output — prefer them over raw send-keys.
The rest of this skill is Mode B — spinning up your own isolated sessions on a private socket, kept cleanly separate from the user's tmux. Use it for throwaway processes the user doesn't need to watch.
SOCKET_DIR=${TMPDIR:-/tmp}/claude-tmux-sockets # well-known dir for all agent sockets
mkdir -p "$SOCKET_DIR"
SOCKET="$SOCKET_DIR/claude.sock" # keep agent sessions separate from your personal tmux
SESSION=claude-node # slug-like names; avoid spaces
tmux -S "$SOCKET" -f /dev/null new -d -s "$SESSION" -n shell # -f /dev/null = stock config, so :0.0 indexing is predictable
tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- 'node' Enter
tmux -S "$SOCKET" capture-pane -p -J -t "$SESSION":0.0 -S -200 # watch output
tmux -S "$SOCKET" kill-session -t "$SESSION" # clean up
After starting a session ALWAYS tell the user how to monitor the session by giving them a command to copy paste:
To monitor this session yourself:
tmux -S "$SOCKET" attach -t claude-node
Or to capture the output once:
tmux -S "$SOCKET" capture-pane -p -J -t claude-node:0.0 -S -200
This must ALWAYS be printed right after a session was started and once again at the end of the tool loop. But the earlier you send it, the happier the user will be.
CLAUDE_TMUX_SOCKET_DIR (defaults to ${TMPDIR:-/tmp}/claude-tmux-sockets) and use tmux -S "$SOCKET" so we can enumerate/clean them. Create the dir first: mkdir -p "$CLAUDE_TMUX_SOCKET_DIR".SOCKET="$CLAUDE_TMUX_SOCKET_DIR/claude.sock".{session}:{window}.{pane}. With the stock config from -f /dev/null (above) indices are 0-based, so :0.0 is the first pane. On the user's own server (Mode A) indices may be 1-based — discover them with find-panes.sh, never assume. Keep session names short (e.g., claude-node, claude-vitest).-S "$SOCKET" consistently to stay on the private socket path.tmux -S "$SOCKET" list-sessions, ./scripts/find-panes.sh -S "$SOCKET" -A../scripts/find-sessions.sh -S "$SOCKET"; add -q partial-name to filter../scripts/find-panes.sh -S "$SOCKET" -A -p../scripts/find-sessions.sh --all (uses CLAUDE_TMUX_SOCKET_DIR or ${TMPDIR:-/tmp}/claude-tmux-sockets).tmux -S "$SOCKET" send-keys -t target -l -- "$cmd"tmux ... send-keys -t target -- $'npx serve -l 8000'.tmux ... send-keys -t target C-c, C-d, C-z, Escape, etc.tmux -S "$SOCKET" capture-pane -p -J -t target -S -200.tmux wait-for (which does not watch pane output).tmux -S "$SOCKET" attach -t "$SESSION"; detach with Ctrl+b d.Some special rules for processes:
node inspect (or node --inspect-brk + a client) by default. It's a line-oriented CLI debugger, so it drives cleanly over send-keys. Reserve lldb/gdb for native or compiled binaries.vite, next dev, vitest, tsx watch — render into the alternate screen buffer and repaint with ANSI escapes. That corrupts send-keys input and leaves capture-pane scrollback noisy or empty. Quiet them down so the pane is scrapeable: export CI=1 (makes vitest run once instead of watching, and drops most spinners/animations) and NO_COLOR=1 (or FORCE_COLOR=0) to strip color codes. When you only need a tool's output, prefer its one-shot mode (vitest run, tsc --noEmit) over its watch mode.run-in-pane.sh does the send/wait/capture in one step — reach for it first.wait-for-text.sh instead of guessing at sleeps:
./scripts/wait-for-text.sh -t "$SESSION":0.0 -p '^> ' -T 15 # Node REPL ready
./scripts/wait-for-text.sh -t web:1.2 -p 'Local:' -e 'EADDRINUSE|Error' -T 30 # dev server up, or fail fast
./scripts/wait-for-text.sh -t build:0.0 --idle 3 -T 120 # wait for a noisy build to go quiet
"Local:" / "ready in" (Vite/Next), "waiting for file changes" (watcher), a test summary like "Tests ", or --idle when there's no reliable string.tmux ... send-keys -- 'node' Enter; wait for ^> ; send code with -l; for multi-line input enter .editor, send the block, then C-d to run it; interrupt a hung evaluation with C-c; exit with .exit or two C-c. Use tsx instead of node when you need a TypeScript-aware REPL.tmux ... send-keys -- 'node inspect ./dist/app.js' Enter (or node inspect $(npx --no-install which tsx) ./src/app.ts); wait for debug> ; drive with cont/c, next/n, step/s, out/o, and repl to inspect state; set breakpoints with setBreakpoint('file.js', 42) or a debugger; statement; pause a running program with C-c; quit via .exit.vite, next dev, tsx watch, vitest): start the process, poll for its ready line (see above), then read the visible pane rather than deep scrollback — these repaint, so capture-pane without -S is usually cleaner. Remember CI=1/NO_COLOR=1 to tame the output.psql, redis-cli, prisma studio CLI prompts, bash): same pattern—start the program, poll for its prompt, then send literal text and Enter.tmux -S "$SOCKET" kill-session -t "$SESSION".tmux -S "$SOCKET" list-sessions -F '#{session_name}' | xargs -r -n1 tmux -S "$SOCKET" kill-session -t.tmux -S "$SOCKET" kill-server.All live in ./scripts/, work on Linux/macOS (bash + tmux + grep; find-panes -p also needs lsof/pgrep), and take -S <socket> to target a private socket (omit to use $TMUX/default). Run any with -h for full options.
-p) listening ports. Defaults to your current window; --session / -A widen the scope. Answers "which pane is my :3000 server?"-t target -- <cmd>: send a command to a shell pane, wait for it to finish (prompt-agnostic sentinel), print only that command's output, and exit with its exit code. Refuses non-shell panes unless --force.-p success regex (exit 0, prints the match), -e error regex (exit 3, fail fast), and/or --idle N (exit 0 once output is quiet for N seconds). -T overall timeout (exit 1); -F for fixed strings.send-keys: refuses editors/pagers/TUIs (where keystrokes aren't commands) unless --force. --enter appends Enter to literal text; --keys sends key names (C-c, Escape). REPLs and shells are allowed.--all to scan every socket under CLAUDE_TMUX_SOCKET_DIR (Mode B discovery/cleanup).npx claudepluginhub tinetti/claude-plugins --plugin tmuxCreates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.