tmux
Remote control tmux sessions for interactive CLIs (python, gdb, etc.) by sending keystrokes and scraping pane output.
By mitsuhiko · 400 installs
npx skills add mitsuhiko/agent-stuff --skill tmux
Source repository · Upstream listing
tmux Skill
Use tmux as a programmable terminal multiplexer for interactive work. Works on Linux and macOS with stock tmux; avoid custom config by using a private socket.
Quickstart (isolated socket)
After starting a session ALWAYS tell the user how to monitor the session by giving them a command to copy paste:
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} , defaults to :0.0 if omitted. Keep names short (e.g., claude py , claude gdb ).
Use S "$SOCKET" consistently to stay on the private socket path. If you need user config, drop f /dev/null ; otherwise f /dev/null gives a clean config.
Inspect: tmux S "$SOCKET" list sessions , tmux S "$SOCKET" list panes a .
Finding sessions
List sessions on your active socket with metadata: ./scripts/find sessions.sh S "$SOCKET" ; add q partial name to filter.
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 L "$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 $'python3 m http.server 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 L "$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 L "$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, use lldb by default
when starting a python interactive shell, always set the PYTHON BASIC REPL=1 environment variable. This is very important as the non basic console interferes with your send keys.
Synchronizing / waiting for prompts
Use timed polling to avoid races with interactive tools. Example: wait for a Python prompt before sending code:
For long running commands, poll for completion text ( "Type quit to exit" , "Program exited" , etc.) before proceeding.
Interactive tool recipes
Python REPL : tmux ... send keys 'python3 q' Enter ; wait for ^ ; send code with l ; interrupt with C c . Always with PYTHON BASIC REPL .
gdb : tmux ... send keys 'gdb quiet ./a.out' Enter ; disable paging tmux ... send keys 'set pagination off' Enter ; break with C c ; issue bt , info locals , etc.; exit via quit then confirm y .
Other TTY apps (ipdb, psql, mysql, node, 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 .
Helper: wait for text.sh
./scripts/wait for text.sh polls a pane for a regex (or fixed string) with a timeout. Works on Linux/macOS with bash + tmux + grep.
t / target pane target (required)
p / pattern regex to match (required); add F for fixed string
T timeout seconds (integer, default 15)
i poll interval seconds (default 0.5)
l history lines to search from the pane (integer, default 1000)
Exits 0 on first match, 1 on timeout. On failure prints the last captured text to stderr to aid debugging.