home / skills / mitsuhiko / agent-stuff / tmux

tmux skill

/skills/tmux

This skill enables programmable tmux sessions to automate interactive CLIs by sending keystrokes and capturing pane output.

npx playbooks add skill mitsuhiko/agent-stuff --skill tmux

Review the files below or copy the command above to add this skill to your agents.

Files (3)
SKILL.md
5.4 KB
---
name: tmux
description: "Remote control tmux sessions for interactive CLIs (python, gdb, etc.) by sending keystrokes and scraping pane output."
license: Vibecoded
---

# 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)

```bash
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-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 command to copy paste:

```
To monitor this session yourself:
  tmux -S "$SOCKET" attach -t claude-lldb

Or to capture the output once:
  tmux -S "$SOCKET" capture-pane -p -J -t claude-lldb: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}`, 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:
  ```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 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.

```bash
./scripts/wait-for-text.sh -t session:0.0 -p 'pattern' [-F] [-T 20] [-i 0.5] [-l 2000]
```

- `-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.

Overview

This skill programs tmux as a controllable terminal multiplexer to run interactive CLIs (Python, gdb, databases, shells) by sending keystrokes and scraping pane output. It isolates agent sessions on a private socket so agent activity does not clash with a user’s personal tmux. The skill is TypeScript-based and designed for Linux and macOS with stock tmux.

How this skill works

The skill creates a tmux server on a private socket and starts named sessions and panes. It sends literal keystrokes to a target pane, polls or captures pane output to detect prompts or completion text, and cleans up sessions when finished. Helper scripts support polling for text, listing sessions, and capturing pane history with joined lines to avoid wrapping artifacts.

When to use it

  • Run an interactive Python REPL or script and exchange code programmatically.
  • Drive a debugger (gdb or lldb) for automated debugging tasks.
  • Interact with TTY-only tools (ipdb, psql, mysql, node, bash) without attaching a user terminal.
  • Isolate agent-driven terminal work to avoid interfering with the user's tmux config.
  • Capture or monitor long-running CLI output and wait for specific prompts or completion messages.

Best practices

  • Always use a private socket directory (CLAUDE_TMUX_SOCKET_DIR) and tmux -S "$SOCKET" to avoid using the user's tmux instance.
  • After starting a session, immediately print a copy/paste monitor command so the user can attach or capture output.
  • Prefer literal send-keys (-l or -- with proper quoting) to avoid shell splitting and use control sequences like C-c for interrupts.
  • Set PYTHON_BASIC_REPL=1 when launching Python interactive shells to keep prompt behavior stable.
  • Poll for prompts or completion text with a timed helper script to avoid race conditions before sending further input.

Example use cases

  • Start a Python REPL, wait for the >>> prompt, then send multi-line code and capture results.
  • Launch gdb --quiet on a binary, disable pagination, run until a breakpoint, collect backtraces and local variables.
  • Spawn a psql or mysql session, run queries programmatically, and capture the last N lines of output.
  • Run an instrumented test that prints a completion message, poll the pane for that message, then gather logs.
  • Provide the user with an attach command so they can monitor or intervene in the running session.

FAQ

How does the user monitor the running session?

Immediately print a copy/paste command such as: tmux -S "$SOCKET" attach -t session-name or to capture output once: tmux -S "$SOCKET" capture-pane -p -J -t session-name:0.0 -S -200

How do I wait for a prompt before sending input?

Use the wait-for-text.sh helper to poll a pane for a regex or fixed string with a timeout, e.g. ./scripts/wait-for-text.sh -t session:0.0 -p '^>>>' -T 15 -l 4000