"Teaches agents to control tmux sessions for interactive CLIs and long-running processes via the Bash tool. Use when running REPLs, debuggers, database shells, long-running servers, or any tool requiring a persistent TTY. Triggers: 'start a REPL', 'run interactively', 'interactive session', 'long-running process', 'background server', 'keep running'. Do NOT use for simple one-shot commands or non-interactive background tasks that the Bash tool handles natively."
Install
npx skillscat add sebnow/configs/tmux Install via the SkillsCat registry.
tmux for Agents
The Bash tool runs commands synchronously with a timeout
and cannot interact with programs that require a persistent TTY.
tmux provides programmatic session control:
create detached sessions, send keystrokes, and capture output,
all through non-interactive Bash commands.
Use tmux when you need to:
- Run interactive programs (REPLs, debuggers, database shells)
- Monitor long-running processes (servers, builds, watchers)
- Send input to a running process over time
Do not use tmux for simple one-shot commands.
The Bash tool handles those directly.
Session Setup
Socket Isolation
Never use the default tmux server.
Always use a dedicated socket to avoid interfering with user sessions.
TMUX_TMPDIR=$(mktemp -d)
TMUX="tmux -L agent"Set TMUX_TMPDIR before any tmux command
so the socket file is created in an isolated directory.
Use TMUX_TMPDIR and -L agent consistently in every command.
Creating Sessions
TMUX_TMPDIR=$(mktemp -d) $TMUX new-session -d -s myrepl -x 200 -y 50Required flags:
-d- detached (never omit; agents cannot attach)-s <name>- named session (required for all subsequent commands)-x 200 -y 50- explicit window size (prevents default 80x24 truncation)
Name sessions descriptively: python-repl, dev-server, debug-gdb.
Listing Sessions
$TMUX list-sessionsCheck for existing sessions before creating new ones
to avoid duplicates.
Sending Input
Literal Text
Use -l (literal) mode to prevent key-name interpretation:
$TMUX send-keys -t myrepl -l 'print("hello world")'
$TMUX send-keys -t myrepl EnterAlways send Enter as a separate command after literal text.
Without -l, tmux interprets strings like C-c as control sequences.
Control Keys
Send control keys without -l:
$TMUX send-keys -t myrepl C-c # interrupt
$TMUX send-keys -t myrepl C-d # EOF
$TMUX send-keys -t myrepl C-l # clear screenMulti-Line Input
For multi-line input,
send each line separately with Enter between them:
$TMUX send-keys -t myrepl -l 'def greet(name):'
$TMUX send-keys -t myrepl Enter
$TMUX send-keys -t myrepl -l ' return f"Hello, {name}"'
$TMUX send-keys -t myrepl Enter
$TMUX send-keys -t myrepl EnterNever paste large blocks at once.
REPLs may misinterpret rapid multi-line input.
Reading Output
Capture the current pane content:
$TMUX capture-pane -t myrepl -p -S -50Flags:
-p- print to stdout (required; without it, output goes to a paste buffer)-S -50- start capture 50 lines back from cursor (negative = history)-J- join wrapped lines (add when output has long lines)
To capture the entire scrollback:
$TMUX capture-pane -t myrepl -p -S -Always limit with -S -N when you only need recent output.
Full scrollback can be very large.
Synchronization
After sending a command,
wait for the program to produce output or return to a prompt.
Poll with a timeout:
for i in $(seq 1 30); do
output=$($TMUX capture-pane -t myrepl -p -S -5)
if echo "$output" | grep -q '>>> \|In \['; then
break
fi
sleep 1
doneAdapt the grep pattern to match the expected prompt:
| Program | Prompt pattern |
|---|---|
| Python | >>> |\\.\\.\\. |
| IPython | In \\[ |
| Node.js | > |
| PostgreSQL | =# |=> |
| GDB | (gdb) |
| Shell | \\$ |
For long-running processes without a prompt (servers, builds),
poll for specific output strings:
for i in $(seq 1 60); do
output=$($TMUX capture-pane -t myrepl -p -S -10)
if echo "$output" | grep -q 'Listening on port'; then
break
fi
sleep 2
doneAlways set a finite upper bound on poll iterations.
Never poll indefinitely.
User Notification
After creating any tmux session,
always print the monitoring commands so the user can observe:
Session 'dev-server' started. Monitor with:
TMUX_TMPDIR=/tmp/tmp.xxxx tmux -L agent attach -t dev-serverInclude the full command with TMUX_TMPDIR path,
since the user needs it to connect to the isolated socket.
Cleanup
Kill sessions when they are no longer needed:
$TMUX kill-session -t myreplKill the entire agent server when all work is done:
$TMUX kill-serverClean up the socket directory:
rm -rf "$TMUX_TMPDIR"Cleanup obligations:
- Kill sessions before creating replacements
- Kill the server when all sessions are done
- Always clean up on task completion, even if errors occurred
- Never leave orphaned sessions running
Common Issues
Python REPL needs extra Enter for blocks:
After indented blocks (def, if, for),
send an extra empty Enter to execute the block.
Stale output after send-keys:
Always poll or sleep before capture-pane.
send-keys returns immediately;
the program needs time to process input.
Socket not found errors:
Verify TMUX_TMPDIR is set to the same directory
used during session creation.
The socket lives at $TMUX_TMPDIR/agent.
Session name collisions:
Use list-sessions to check before creating.
Kill stale sessions rather than using duplicate names.
Output truncated at 80 columns:
Set explicit window dimensions with -x 200 -y 50
when creating the session.
Examples
Python REPL
TMUX_TMPDIR=$(mktemp -d) tmux -L agent new-session -d -s python -x 200 -y 50 python3
# Wait for prompt
for i in $(seq 1 15); do
output=$(TMUX_TMPDIR=$TMUX_TMPDIR tmux -L agent capture-pane -t python -p -S -3)
if echo "$output" | grep -q '>>>'; then break; fi
sleep 1
done
# Send a command
TMUX_TMPDIR=$TMUX_TMPDIR tmux -L agent send-keys -t python -l 'import sys; print(sys.version)'
TMUX_TMPDIR=$TMUX_TMPDIR tmux -L agent send-keys -t python Enter
sleep 1
TMUX_TMPDIR=$TMUX_TMPDIR tmux -L agent capture-pane -t python -p -S -5Long-Running Server
TMUX_TMPDIR=$(mktemp -d) tmux -L agent new-session -d -s server -x 200 -y 50 'npm run dev'
# Wait for server ready
for i in $(seq 1 30); do
output=$(TMUX_TMPDIR=$TMUX_TMPDIR tmux -L agent capture-pane -t server -p -S -10)
if echo "$output" | grep -q 'ready\|listening\|started'; then break; fi
sleep 2
done
echo "Session 'server' started. Monitor with:"
echo " TMUX_TMPDIR=$TMUX_TMPDIR tmux -L agent attach -t server"Debugger Session
TMUX_TMPDIR=$(mktemp -d) tmux -L agent new-session -d -s gdb -x 200 -y 50 'gdb ./myprogram'
# Wait for GDB prompt
for i in $(seq 1 15); do
output=$(TMUX_TMPDIR=$TMUX_TMPDIR tmux -L agent capture-pane -t gdb -p -S -3)
if echo "$output" | grep -q '(gdb)'; then break; fi
sleep 1
done
# Set breakpoint and run
TMUX_TMPDIR=$TMUX_TMPDIR tmux -L agent send-keys -t gdb -l 'break main'
TMUX_TMPDIR=$TMUX_TMPDIR tmux -L agent send-keys -t gdb Enter
sleep 1
TMUX_TMPDIR=$TMUX_TMPDIR tmux -L agent send-keys -t gdb -l 'run'
TMUX_TMPDIR=$TMUX_TMPDIR tmux -L agent send-keys -t gdb Enter