skills/tmux/SKILL.md
Remote control tmux sessions for interactive CLIs (python, gdb, etc.) by sending keystrokes and scraping pane output.
npx skillsauth add MansoorMajeed/Clawd tmuxInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
Use tmux (not the Bash tool) when:
sudo -- tmux lets the user type the password in the attached terminalCRITICAL: Resolve the socket path to an absolute path in your FIRST Bash call. The $TMPDIR variable can be empty or wrong between separate Bash tool invocations.
# Resolve socket path ONCE (absolute path, no $TMPDIR dependency)
SOCKET="$(getconf DARWIN_USER_TEMP_DIR 2>/dev/null || echo /tmp)claude-tmux-sockets/claude.sock"
mkdir -p "$(dirname "$SOCKET")"
echo "SOCKET=$SOCKET" # save this -- reuse in ALL subsequent calls
Then create and use sessions:
SESSION=claude-python
tmux -S "$SOCKET" new -d -s "$SESSION" -n shell
# NOTE: Window may be :0 or :1 depending on shell/config. Verify with list-windows.
tmux -S "$SOCKET" list-windows -t "$SESSION" # check actual window number
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.
$(getconf DARWIN_USER_TEMP_DIR 2>/dev/null || echo /tmp)claude-tmux-sockets/ and use tmux -S "$SOCKET".SOCKET="$(getconf DARWIN_USER_TEMP_DIR 2>/dev/null || echo /tmp)claude-tmux-sockets/claude.sock".${TMPDIR:-/tmp} in compound && chains across Bash tool calls -- it can expand to empty. Always resolve to an absolute path first.{session}:{window}.{pane}. Keep names short (e.g., claude-py, claude-gdb).list-windows before targeting:
tmux -S "$SOCKET" list-windows -t "$SESSION" # check actual window number
-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.tmux -S "$SOCKET" list-sessions, tmux -S "$SOCKET" list-panes -a.After any disruption (crash, context loss, session resume), always re-discover before sending:
tmux -S "$SOCKET" list-sessions # verify session exists
tmux -S "$SOCKET" list-windows -t "$SESSION" # get actual window number
# THEN use the window number from list-windows output
tmux -S "$SOCKET" send-keys -t "$SESSION":1.0 ... # use verified number
Never assume the window number from memory -- always verify.
./scripts/find-sessions.sh -S "$SOCKET"; add -q partial-name to filter../scripts/find-sessions.sh --all (uses CLAUDE_TMUX_SOCKET_DIR or ${TMPDIR:-/tmp}/claude-tmux-sockets).-l: tmux ... send-keys -t target -- 'command text' Enter -- Enter is a tmux key name, sent as a keypress.-l (literal mode): tmux ... send-keys -t target -l 'text' -- ALL args after -l are literal text. Enter after -l sends the string "Enter", NOT a keypress. To include a newline, use $'text\n':
tmux -S "$SOCKET" send-keys -t target -l $'echo hello\n'
-l): tmux ... send-keys -t target C-c, C-d, C-z, Escape, etc.NEVER send multi-line code or code with special characters (parentheses, quotes, $, #) via send-keys. The host bash interprets these before tmux sees them, causing syntax errors in the pane.
Always write to a file first, then execute:
# Write the script via tmux heredoc (use $'\n' to include newline with -l)
tmux -S "$SOCKET" send-keys -t target -l $'cat > /tmp/myscript.py << \'PYEOF\'\nimport socket\nprint("hello world")\nPYEOF\n'
# Wait for the shell prompt to return
./scripts/wait-for-text.sh -S "$SOCKET" -t target -p '\\$' -T 5
# Execute the file with sentinel
tmux -S "$SOCKET" send-keys -t target -l $'python3 /tmp/myscript.py; echo "===DONE==="\n'
./scripts/wait-for-text.sh -S "$SOCKET" -t target -p '===DONE===' -F -T 30
NEVER use sleep N && capture-pane. This wastes time (waits the full N even if the command finishes in 0.5s) and can miss output (if the command takes longer than N).
ALWAYS append a sentinel marker and poll with wait-for-text.sh:
# 1. Send command with sentinel appended
tmux -S "$SOCKET" send-keys -t target -l $'apt install -y foo; echo "===DONE==="\n'
# 2. Poll for sentinel (exits instantly when found, polls every 0.5s)
./scripts/wait-for-text.sh -S "$SOCKET" -t target -p '===DONE===' -F -T 120
# 3. NOW read the output
tmux -S "$SOCKET" capture-pane -p -J -t target -S -200
Why this is mandatory:
sleep 30 wastes 29.5s if the command finishes in 0.5ssleep 10 misses output if the command takes 11sFor interactive prompts (Python >>>, gdb (gdb)), use wait-for-text.sh with the prompt regex instead of a sentinel.
tmux -S "$SOCKET" capture-pane -p -J -t target -S -200.tmux wait-for (which does not watch pane output).tmux -S "$SOCKET" attach -t "$SESSION"; detach with Ctrl+b d.Some special rules for processes:
PYTHON_BASIC_REPL=1 environment variable. This is very important as the non-basic console interferes with your send-keys.tmux ... send-keys -- 'PYTHON_BASIC_REPL=1 python3 -q' Enter; wait for ^>>>; send code with -l; interrupt with C-c.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.wait-for-text.sh between sends, capture output to verify state:
tmux -S "$SOCKET" send-keys -t target -- 'r' Enter
./scripts/wait-for-text.sh -S "$SOCKET" -t target -p 'Enter value' -T 5
tmux -S "$SOCKET" send-keys -t target -- '1560' Enter
./scripts/wait-for-text.sh -S "$SOCKET" -t target -p 'saved' -T 5
tmux -S "$SOCKET" kill-session -t "$SESSION".tmux -S "$SOCKET" list-sessions -F '#{session_name}' | xargs -r -n1 tmux -S "$SOCKET" kill-session -t.tmux -S "$SOCKET" kill-server../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 -S "$SOCKET" -t session:0.0 -p 'pattern' [-F] [-T 20] [-i 0.5] [-l 2000]
-S/--socket tmux socket path (passed as tmux -S)-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)tools
Allows to interact with web pages by performing actions such as clicking buttons, filling out forms, and navigating links. It works by remote controlling Google Chrome or Chromium browsers using the Chrome DevTools Protocol (CDP). When Claude needs to browse the web, it can use this skill to do so.
development
Update llm-context/ and CLAUDE.md to reflect recent code changes.
development
Fetch a URL or convert a local file (PDF/DOCX/HTML/etc.) into Markdown using `uvx markitdown`, optionally it can summarize
testing
Ship — run checks, bump version, changelog, push, create PR.