tmux 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.
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.
Mode A — observe & control the user's tmux
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
Writing into the user's panes
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:
- Check what's running first (the
[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.
- Prefer your own surface for new work. To run something long-lived, open a window the user can watch rather than hijacking a pane:
tmux new-window -n claude-task (or a fresh session), then drive that. They still see it; you don't clobber their work.
- Send deliberately. Use
-l for literal text; send control keys (C-c, Enter) as separate calls. Then capture-pane to confirm the effect before moving on.
- Say what you touched. Tell the user which pane you sent to and give them a
capture-pane/attach command to verify.
- Confirm destructive sends. Interrupting a server they're running, or sending input that changes state, deserves a heads-up first unless they've already told you to go ahead.
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.
Quickstart (isolated socket)
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.
Socket convention
- Agents MUST place tmux sockets under
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".
- Default socket path to use unless you must isolate further:
SOCKET="$CLAUDE_TMUX_SOCKET_DIR/claude.sock".
Targeting panes and naming
- Target format:
{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).
- Use
-S "$SOCKET" consistently to stay on the private socket path.
- Inspect:
tmux -S "$SOCKET" list-sessions, ./scripts/find-panes.sh -S "$SOCKET" -A.
Finding sessions
- List sessions on your active socket with metadata:
./scripts/find-sessions.sh -S "$SOCKET"; add -q partial-name to filter.
- List panes with command, cwd, and ports:
./scripts/find-panes.sh -S "$SOCKET" -A -p.
- Scan all sockets under the shared directory:
./scripts/find-sessions.sh --all (uses CLAUDE_TMUX_SOCKET_DIR or ${TMPDIR:-/tmp}/claude-tmux-sockets).
Sending input safely
- Prefer literal sends to avoid shell splitting:
tmux -S "$SOCKET" send-keys -t target -l -- "$cmd"
- When composing inline commands, use single quotes or ANSI C quoting to avoid expansion:
tmux ... send-keys -t target -- $'npx serve -l 8000'.
- To send control keys:
tmux ... send-keys -t target C-c, C-d, C-z, Escape, etc.
Watching output
- Capture recent history (joined lines to avoid wrapping artifacts):
tmux -S "$SOCKET" capture-pane -p -J -t target -S -200.
- For continuous monitoring, poll with the helper script (below) instead of
tmux wait-for (which does not watch pane output).
- You can also temporarily attach to observe:
tmux -S "$SOCKET" attach -t "$SESSION"; detach with Ctrl+b d.
- When giving instructions to a user, explicitly print a copy/paste monitor command alongside the action don't assume they remembered the command.
Spawning Processes
Some special rules for processes:
- When asked to debug Node/TypeScript, reach for
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.
- Screen-repainting tools — dev servers and watchers like
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.
Synchronizing / waiting for prompts
Interactive tool recipes
- Node REPL:
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.
- node inspect (the gdb analog):
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.
- Dev servers & watchers (
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.
- Other TTY apps (
psql, redis-cli, prisma studio CLI prompts, bash): same pattern—start the program, poll for its prompt, then send literal text and Enter.
Cleanup
- Kill a session when done:
tmux -S "$SOCKET" kill-session -t "$SESSION".
- Kill all sessions on a socket:
tmux -S "$SOCKET" list-sessions -F '#{session_name}' | xargs -r -n1 tmux -S "$SOCKET" kill-session -t.
- Remove everything on the private socket:
tmux -S "$SOCKET" kill-server.
Bundled scripts
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.
- find-panes.sh — list panes with location, command, cwd, and (with
-p) listening ports. Defaults to your current window; --session / -A widen the scope. Answers "which pane is my :3000 server?"
- run-in-pane.sh —
-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.
- wait-for-text.sh — poll a pane until a condition is met:
-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.
- safe-send.sh — guarded
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.
- find-sessions.sh — list sessions on a socket, or
--all to scan every socket under CLAUDE_TMUX_SOCKET_DIR (Mode B discovery/cleanup).
1---2name: tmux3description: 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.4license: Vibecoded5---67# tmux Skill89Use 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.1011If 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.1213## Mode A — observe & control the user's tmux1415This 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.1617Confirm you're inside their tmux and find the server (`$TMUX` is `socket,pid,session`; the socket is the first field):1819```bash20[ -n "$TMUX" ] && echo "in tmux — socket=${TMUX%%,*} pane=$TMUX_PANE"21```2223Plain `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:2425```bash26./scripts/find-panes.sh -p # panes in YOUR window + listening ports27./scripts/find-panes.sh --session -p # widen to the whole session28```2930**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).3132Read a pane's output — safe, do it freely:3334```bash35tmux capture-pane -p -J -t projects:1.2 -S -200 # scrape a dev server's recent log36```3738Run 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):3940```bash41./scripts/run-in-pane.sh -t projects:1.3 -- pnpm vitest run src/auth42```4344Send keystrokes into a pane through the guard, which refuses editors/pagers (**read the safety rules below first**):4546```bash47./scripts/safe-send.sh -t projects:1.2 --enter -- 'rs' # type 'rs' + Enter (e.g. nodemon restart)48./scripts/safe-send.sh -t projects:1.2 --keys -- C-c # control keys49```5051## Writing into the user's panes5253Reading 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:5455- **Check what's running first** (the `[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.56- **Prefer your own surface for new work.** To run something long-lived, open a window the user can watch rather than hijacking a pane: `tmux new-window -n claude-task` (or a fresh session), then drive that. They still see it; you don't clobber their work.57- **Send deliberately.** Use `-l` for literal text; send control keys (`C-c`, `Enter`) as separate calls. Then `capture-pane` to confirm the effect before moving on.58- **Say what you touched.** Tell the user which pane you sent to and give them a `capture-pane`/`attach` command to verify.59- **Confirm destructive sends.** Interrupting a server they're running, or sending input that changes state, deserves a heads-up first unless they've already told you to go ahead.6061`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`.6263---6465The 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.6667## Quickstart (isolated socket)6869```bash70SOCKET_DIR=${TMPDIR:-/tmp}/claude-tmux-sockets # well-known dir for all agent sockets71mkdir -p "$SOCKET_DIR"72SOCKET="$SOCKET_DIR/claude.sock" # keep agent sessions separate from your personal tmux73SESSION=claude-node # slug-like names; avoid spaces74tmux -S "$SOCKET" -f /dev/null new -d -s "$SESSION" -n shell # -f /dev/null = stock config, so :0.0 indexing is predictable75tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- 'node' Enter76tmux -S "$SOCKET" capture-pane -p -J -t "$SESSION":0.0 -S -200 # watch output77tmux -S "$SOCKET" kill-session -t "$SESSION" # clean up78```7980After starting a session ALWAYS tell the user how to monitor the session by giving them a command to copy paste:8182```83To monitor this session yourself:84 tmux -S "$SOCKET" attach -t claude-node8586Or to capture the output once:87 tmux -S "$SOCKET" capture-pane -p -J -t claude-node:0.0 -S -20088```8990This 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.9192## Socket convention9394- Agents MUST place tmux sockets under `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"`.95- Default socket path to use unless you must isolate further: `SOCKET="$CLAUDE_TMUX_SOCKET_DIR/claude.sock"`.9697## Targeting panes and naming9899- Target format: `{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`).100- Use `-S "$SOCKET"` consistently to stay on the private socket path.101- Inspect: `tmux -S "$SOCKET" list-sessions`, `./scripts/find-panes.sh -S "$SOCKET" -A`.102103## Finding sessions104105- List sessions on your active socket with metadata: `./scripts/find-sessions.sh -S "$SOCKET"`; add `-q partial-name` to filter.106- List panes with command, cwd, and ports: `./scripts/find-panes.sh -S "$SOCKET" -A -p`.107- Scan all sockets under the shared directory: `./scripts/find-sessions.sh --all` (uses `CLAUDE_TMUX_SOCKET_DIR` or `${TMPDIR:-/tmp}/claude-tmux-sockets`).108109## Sending input safely110111- Prefer literal sends to avoid shell splitting: `tmux -S "$SOCKET" send-keys -t target -l -- "$cmd"`112- When composing inline commands, use single quotes or ANSI C quoting to avoid expansion: `tmux ... send-keys -t target -- $'npx serve -l 8000'`.113- To send control keys: `tmux ... send-keys -t target C-c`, `C-d`, `C-z`, `Escape`, etc.114115## Watching output116117- Capture recent history (joined lines to avoid wrapping artifacts): `tmux -S "$SOCKET" capture-pane -p -J -t target -S -200`.118- For continuous monitoring, poll with the helper script (below) instead of `tmux wait-for` (which does not watch pane output).119- You can also temporarily attach to observe: `tmux -S "$SOCKET" attach -t "$SESSION"`; detach with `Ctrl+b d`.120- When giving instructions to a user, **explicitly print a copy/paste monitor command** alongside the action don't assume they remembered the command.121122## Spawning Processes123124Some special rules for processes:125126- When asked to debug Node/TypeScript, reach for `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.127- Screen-repainting tools — dev servers and watchers like `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.128129## Synchronizing / waiting for prompts130131- To just run a command and capture its output, `run-in-pane.sh` does the send/wait/capture in one step — reach for it first.132- To wait for a specific signal yourself, poll with `wait-for-text.sh` instead of guessing at sleeps:133 ```bash134 ./scripts/wait-for-text.sh -t "$SESSION":0.0 -p '^> ' -T 15 # Node REPL ready135 ./scripts/wait-for-text.sh -t web:1.2 -p 'Local:' -e 'EADDRINUSE|Error' -T 30 # dev server up, or fail fast136 ./scripts/wait-for-text.sh -t build:0.0 --idle 3 -T 120 # wait for a noisy build to go quiet137 ```138- Good ready/completion signals: `"Local:"` / `"ready in"` (Vite/Next), `"waiting for file changes"` (watcher), a test summary like `"Tests "`, or `--idle` when there's no reliable string.139140## Interactive tool recipes141142- **Node REPL**: `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.143- **node inspect** (the gdb analog): `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`.144- **Dev servers & watchers** (`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.145- **Other TTY apps** (`psql`, `redis-cli`, `prisma studio` CLI prompts, `bash`): same pattern—start the program, poll for its prompt, then send literal text and Enter.146147## Cleanup148149- Kill a session when done: `tmux -S "$SOCKET" kill-session -t "$SESSION"`.150- Kill all sessions on a socket: `tmux -S "$SOCKET" list-sessions -F '#{session_name}' | xargs -r -n1 tmux -S "$SOCKET" kill-session -t`.151- Remove everything on the private socket: `tmux -S "$SOCKET" kill-server`.152153## Bundled scripts154155All 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.156157- **find-panes.sh** — list panes with location, command, cwd, and (with `-p`) listening ports. Defaults to your current window; `--session` / `-A` widen the scope. Answers "which pane is my `:3000` server?"158- **run-in-pane.sh** — `-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`.159- **wait-for-text.sh** — poll a pane until a condition is met: `-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.160- **safe-send.sh** — guarded `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.161- **find-sessions.sh** — list sessions on a socket, or `--all` to scan every socket under `CLAUDE_TMUX_SOCKET_DIR` (Mode B discovery/cleanup).