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.
When to Reach for tmux
Default to tmux for any command that isn't a quick one-shot. If you know the command will:
- •Produce streaming output you need to read and react to
- •Ask for input, confirmations, or selections
- •Take more than a few seconds and you want to check progress
- •Run interactively (installers, wizards, watch-mode tools, test runners)
...then start it in tmux instead of blocking on bash. The workflow is:
- •Start the command in a tmux session
- •Check output with
capture-paneperiodically - •Interact — send input, respond to prompts, ctrl-c if needed
- •Clean up the session when done
This is faster because you see partial output immediately, react to errors early, and never get stuck waiting for a blocking command to finish. You stay in control.
Don't use tmux for: quick one-shot commands (git status, ls, cat, grep), simple non-interactive CLIs, or file operations.
Quickstart (isolated socket)
SOCKET_DIR=${TMPDIR:-/tmp}/pi-tmux-sockets # well-known dir for all agent sockets
mkdir -p "$SOCKET_DIR"
SOCKET="$SOCKET_DIR/pi.sock" # keep agent sessions separate from your personal tmux
SESSION=pi-python # slug-like names; avoid spaces
tmux -S "$SOCKET" new -d -s "$SESSION" -n shell
tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- 'python3 -q' 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 ready-to-copy-paste command with the fully resolved absolute socket path — never use $SOCKET, $TMPDIR, or any variable. The user's shell may not have these set.
Example (adapt session name):
To monitor this session yourself: tmux -S /var/folders/xx/.../pi-tmux-sockets/pi.sock attach -t pi-lldb
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
PI_TMUX_SOCKET_DIR(defaults to${TMPDIR:-/tmp}/pi-tmux-sockets) and usetmux -S "$SOCKET"so we can enumerate/clean them. Create the dir first:mkdir -p "$PI_TMUX_SOCKET_DIR". - •Default socket path to use unless you must isolate further:
SOCKET="$PI_TMUX_SOCKET_DIR/pi.sock".
Targeting panes and naming
- •Target format:
{session}:{window}.{pane}, defaults to:0.0if omitted. Keep names short (e.g.,pi-py,pi-gdb). - •Use
-S "$SOCKET"consistently to stay on the private socket path. If you need user config, drop-f /dev/null; otherwise-f /dev/nullgives 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-nameto filter. - •Scan all sockets under the shared directory:
./scripts/find-sessions.sh --all(usesPI_TMUX_SOCKET_DIRor${TMPDIR:-/tmp}/pi-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 withCtrl+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=1environment 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:
bash
./scripts/wait-for-text.sh -t "$SESSION":0.0 -p '^>>>' -T 15 -l 4000
- •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 withC-c. Always withPYTHON_BASIC_REPL. - •gdb:
tmux ... send-keys -- 'gdb --quiet ./a.out' Enter; disable pagingtmux ... send-keys -- 'set pagination off' Enter; break withC-c; issuebt,info locals, etc.; exit viaquitthen confirmy. - •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.
./scripts/wait-for-text.sh -t session:0.0 -p 'pattern' [-F] [-T 20] [-i 0.5] [-l 2000]
- •
-t/--targetpane target (required) - •
-p/--patternregex to match (required); add-Ffor fixed string - •
-Ttimeout seconds (integer, default 15) - •
-ipoll interval seconds (default 0.5) - •
-lhistory 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.