tmux-control
Skill2 starsChanged 13 days agoMITView on GitHub
Use when sending commands to tmux panes, reading pane output, creating windows/panes, or monitoring tmux sessions. Covers reliable targeting, synchronization, and output capture patterns.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- claude-world/claude-agenttheir own version, first seen Mar 22, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- claude-world/claude-agent★ 5same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository17 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- skills/agent-workspace/templates/CLAUDE.mdCLAUDE.md
- agent-workspaceSkill
- ai-council-reviewSkill
- ai-reviewSkill
- apple-mailSkill
- apple-notesSkill
- ballotSkill
- chrome-browserSkill
- dossierSkill
- lab-notesSkill
- pandocSkill
- paprika-recipesSkill
- pdf-zineSkill
- private-podcast-feedSkill
- send-to-kindleSkill
- text-to-speechSkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 7 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈7
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 18
Use
wait-for: instead of sleep for synchronization - Very specific: line 21
Verify targets exist: before sending — use list-panes -a to check panes, list-windows for windows, has-session for sessions
- Distinctive: line 79
If you must use send-keys on a new pane, add a brief delay:
- Distinctive: line 97
Outer quotes: use single quotes around the command to prevent local shell expansion
- Distinctive: line 98
Inner quotes: if the command itself needs quotes, use double quotes inside singles, or escape
- Distinctive: line 144
When output exceeds scrollback or you need the complete result:
- Distinctive: line 340
Default: 300s timeout, 2s poll interval
- Distinctive: line 405
If you encounter a tmux pattern that fails, a missing recipe, or incorrect guidance in this skill, don't just work around it — fix the …
- Distinctive: line 410
Never silently work around a skill gap.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
tmux Control Patterns
Reliable patterns for programmatic tmux interaction. The #1 source of bugs is targeting — follow these rules strictly.
Golden Rules
- Use unique IDs for targeting:
%N(pane),@N(window),$NAME(session) — never bare indexes - Use
wait-forinstead ofsleepfor synchronization - Pass commands to
new-window/split-windowdirectly — avoid send-keys to freshly created panes (race condition) - Always use
-d(detached) withnew-windowandsplit-window— never steal focus from the user's active window - Verify targets exist before sending — use
list-panes -ato check panes,list-windowsfor windows,has-sessionfor sessions - Use full binary paths in commands sent to panes — the pane's environment may differ from yours
Targeting
The format is SESSION:WINDOW.PANE. All three parts are optional but be explicit.
Discover targets first — always
# List all panes with their IDs and human-readable positions
tmux list-panes -a -F '#{pane_id} #{session_name}:#{window_index}.#{pane_index} #{pane_current_command}'
# List sessions
tmux list-sessions -F '#{session_id} #{session_name}'
# List windows in current session
tmux list-windows -F '#{window_id} #{window_index} #{window_name}'Targeting syntax
| Target | Meaning | Reliability |
%42 | Pane ID (unique, stable) | Best |
@3 | Window ID (unique, stable) | Good |
$main | Session by name | Good |
main:2.0 | Session:Window.Pane by index | Fragile — indexes shift |
:2 | Window 2 in current session | OK for interactive use |
-t 2 | Ambiguous (session? window?) | Avoid |
Capture pane ID at creation
# Best pattern: capture the ID when you create the pane
# Pass the command directly to avoid the send-keys race condition
PANE_ID=$(tmux split-window -d -P -F '#{pane_id}' 'echo hello; exec bash')
# or
PANE_ID=$(tmux new-window -d -P -F '#{pane_id}' 'echo hello; exec bash')Creating Panes and Windows
Preferred: pass command directly (avoids race condition)
# Command runs as soon as shell is ready — no race (-d = detached, don't steal focus)
tmux new-window -d 'echo "hello from new window"; exec bash'
tmux split-window -d 'npm run dev; exec bash'
# Capture the ID too
PANE_ID=$(tmux new-window -d -P -F '#{pane_id}' 'npm test; exec bash')Why send-keys to new panes is fragile
send-keys fires immediately, but the shell in a new pane may not be initialized yet (especially with heavy shell configs like oh-my-zsh). The command arrives before the prompt is ready and gets lost.
If you must use send-keys on a new pane, add a brief delay:
PANE_ID=$(tmux split-window -d -P -F '#{pane_id}')
sleep 0.5 # let shell initialize — fragile but sometimes necessary
tmux send-keys -t "$PANE_ID" 'your command' EnterSending Commands
Basic pattern
tmux send-keys -t "$TARGET" 'your command here' EnterQuoting rules
- Outer quotes: use single quotes around the command to prevent local shell expansion
- Inner quotes: if the command itself needs quotes, use double quotes inside singles, or escape
- Pipes and redirects: work fine inside quotes
# Simple
tmux send-keys -t "$PANE" 'ls -la /tmp' Enter
# With pipes (single-quoted, so pipe is literal)
tmux send-keys -t "$PANE" 'ps aux | grep node' Enter
# With inner quotes
tmux send-keys -t "$PANE" 'echo "hello world" > /tmp/out.txt' Enter
# Complex: write to temp file and source it
echo 'complex command "with" pipes | and stuff' > /tmp/tmux-cmd.sh
tmux send-keys -t "$PANE" 'bash /tmp/tmux-cmd.sh' EnterNested Claude Code sessions
Running claude inside an existing Claude Code session fails due to a guard variable. Workaround:
tmux send-keys -t "$PANE" 'env -u CLAUDECODE claude -p "your prompt"' EnterReading Output
capture-pane (visible buffer)
# Last screenful
tmux capture-pane -t "$PANE" -p
# Last N lines
tmux capture-pane -t "$PANE" -p -S -20
# Full scrollback, joined wrapped lines
tmux capture-pane -t "$PANE" -p -S - -J
# With ANSI colors preserved
tmux capture-pane -t "$PANE" -p -eFile-based pattern (for long output)
When output exceeds scrollback or you need the complete result:
# Use a unique channel to avoid collisions with concurrent commands
CHAN="cmd-done-$$-$RANDOM"
tmux send-keys -t "$PANE" "your-command > /tmp/result.out 2>&1; tmux wait-for -S $CHAN" Enter
tmux wait-for "$CHAN"
# Read the complete output
cat /tmp/result.outPrompt detection (is the pane idle?)
tmux capture-pane -t "$PANE" -p | tail -1 | grep -qE '(\$|>|#|%)\s*$' && echo "idle" || echo "busy"Caution: capture-pane returns empty if the pane hasn't rendered yet. Don't check immediately after creating a pane.
Synchronization with wait-for
wait-for provides channel-based synchronization — far more reliable than sleep.
Pattern: run command and wait for completion
# Generate a unique channel name
CHAN="done-$$-$RANDOM"
# Send command with signal on completion
tmux send-keys -t "$PANE" "your-command; tmux wait-for -S $CHAN" Enter
# Block until command completes
tmux wait-for "$CHAN"
# Now safely capture output or proceed
tmux capture-pane -t "$PANE" -p -S -20Pattern: timeout with wait-for
CHAN="done-$$-$RANDOM"
tmux send-keys -t "$PANE" "long-command; tmux wait-for -S $CHAN" Enter
# Use `timeout` utility — exit 0 = command completed, exit 124 = timed out
if timeout 30 tmux wait-for "$CHAN"; then
echo "Command completed"
else
echo "Command timed out"
fiMonitoring
pipe-pane: stream output to file
# Start logging pane output to a file
tmux pipe-pane -t "$PANE" -o 'cat >> /tmp/pane-log.txt'
# Stop logging
tmux pipe-pane -t "$PANE"
# Stream through a filter (e.g., watch for errors)
tmux pipe-pane -t "$PANE" -o 'grep --line-buffered ERROR >> /tmp/errors.txt'Note: only one pipe per pane. Setting a new pipe replaces the old one.
monitor-silence: detect idle pane
# Alert after 10 seconds of no output (window-level option)
tmux set-option -t "$WINDOW" monitor-silence 10
# Check if silence alert triggered
tmux display-message -t "$WINDOW" -p '#{window_silence_flag}'monitor-activity: detect new output
# Flag when any output appears in a background window
tmux set-option -t "$WINDOW" monitor-activity onmacOS-Specific
Full Disk Access (FDA)
tmux sessions started from a GUI terminal (Terminal.app, iTerm2) inherit FDA. Sessions started over SSH do not. This matters for accessing ~/Documents/, ~/Desktop/, and NFS mounts.
Rule: for file system operations requiring FDA, send commands to a tmux pane that was created from a GUI terminal — never to an SSH-initiated session.
PATH and environment
The pane's shell environment may differ from yours — don't assume your aliases, functions, or PATH are available:
- Don't rely on aliases — use full commands (e.g.,
/opt/homebrew/bin/tmuxnottmux) - PATH may differ — use absolute paths for non-standard binaries
- Shell functions may not exist — write scripts to temp files if needed
Recipes
Run command and get output
# 1. Discover target
PANE=$(tmux list-panes -F '#{pane_id}' | head -1)
# 2. Run with wait-for
CHAN="cmd-$$"
tmux send-keys -t "$PANE" "whoami; tmux wait-for -S $CHAN" Enter
tmux wait-for "$CHAN"
# 3. Capture result
tmux capture-pane -t "$PANE" -p -S -5Start long process and check later
# 1. Create dedicated window
PANE=$(tmux new-window -d -P -F '#{pane_id}' -n 'build')
# 2. Send the long-running command (with completion marker)
tmux send-keys -t "$PANE" 'npm run build > /tmp/build.out 2>&1; echo "BUILD_DONE" >> /tmp/build.out' Enter
# 3. Later: check if done
grep -q "BUILD_DONE" /tmp/build.out 2>/dev/null && echo "finished" || echo "still running"
# 4. Read full output
cat /tmp/build.outMonitor pane for completion
# 1. Start logging
tmux pipe-pane -t "$PANE" -o 'cat >> /tmp/pane-watch.txt'
# 2. Send command
tmux send-keys -t "$PANE" 'make all' Enter
# 3. Poll for completion marker (or use wait-for instead)
while ! grep -q '\$' <(tail -1 /tmp/pane-watch.txt 2>/dev/null); do
sleep 2
done
echo "Command completed"
# 4. Clean up
tmux pipe-pane -t "$PANE"Helper Scripts
Two helper scripts in ${CLAUDE_SKILL_DIR}/scripts/ wrap common patterns into single commands. Always use the full ${CLAUDE_SKILL_DIR}/scripts/ path — these scripts are bundled with this skill, not in the project being worked on.
tmux-run.sh — run command and get output
# Send command, wait for completion, return output
${CLAUDE_SKILL_DIR}/scripts/tmux-run.sh -t %42 'npm test'
# With timeout (default: 120s)
${CLAUDE_SKILL_DIR}/scripts/tmux-run.sh -t %42 -T 60 'make build'
# Capture output in a variable
output=$(${CLAUDE_SKILL_DIR}/scripts/tmux-run.sh -t %42 -q 'git status')
# Exit code is forwarded from the remote command
${CLAUDE_SKILL_DIR}/scripts/tmux-run.sh -t %42 'npm test' || echo "tests failed"- stdout: command output only
- stderr: status messages (suppress with
-q) - Exit code: mirrors remote command (128 = timeout)
- Requires pane ID (
%Nformat)
tmux-watch.sh — wait for pattern in pane output
# Wait for a build to finish
${CLAUDE_SKILL_DIR}/scripts/tmux-watch.sh -t %42 'BUILD_DONE'
# With timeout and poll interval
${CLAUDE_SKILL_DIR}/scripts/tmux-watch.sh -t %42 -T 300 -i 5 'Tests:.*passed'
# Wait for shell prompt (agent finished)
${CLAUDE_SKILL_DIR}/scripts/tmux-watch.sh -t %42 '\$\s*$'- Pattern is extended regex (grep -E)
- stdout: matching line(s)
- Exit 0 = found, 1 = timeout, 2 = pane not found
- Default: 300s timeout, 2s poll interval
Multi-Agent Patterns
Patterns for running multiple Claude Code instances (or any agents) in parallel tmux panes.
Spawn parallel agents
# Create a multi-pane layout
# -d on new-window prevents stealing focus from the user
PANE1=$(tmux new-window -d -n 'agents' -P -F '#{pane_id}')
PANE2=$(tmux split-window -d -h -t "$PANE1" -P -F '#{pane_id}')
PANE3=$(tmux split-window -d -v -t "$PANE2" -P -F '#{pane_id}')
# Launch agents in each pane
# env -u CLAUDECODE allows nested Claude instances
for PANE in $PANE1 $PANE2 $PANE3; do
tmux send-keys -t "$PANE" \
"env -u CLAUDECODE claude -p 'your task...' --output-format json > /tmp/agent-${PANE}.json 2>&1; echo DONE > /tmp/agent-${PANE}.signal" \
Enter
done
# Wait for all agents to complete
for PANE in $PANE1 $PANE2 $PANE3; do
while [ ! -f "/tmp/agent-${PANE}.signal" ]; do sleep 5; done
done
# Collect results
for PANE in $PANE1 $PANE2 $PANE3; do
echo "=== Agent $PANE ==="
jq -r '.result' "/tmp/agent-${PANE}.json"
doneMonitor agents with tmux-watch.sh
# Watch each pane for shell prompt (agent finished)
for PANE in $PANE1 $PANE2 $PANE3; do
${CLAUDE_SKILL_DIR}/scripts/tmux-watch.sh -t "$PANE" -T 600 -q '\$\s*$' &
done
wait # blocks until all watchers return
echo "All agents complete"File-based coordination (proven pattern)
The ralph-sprint system proves this coordination model at scale:
- Git as shared memory — agents coordinate via branches, PRs, committed files
- File-based signals — write marker files, poll for existence
- Promise signals — use
<promise>COMPLETE</promise>in agent output for unambiguous completion detection (plain-text markers get false-positives from echoed prompt text) - Stateless iterations — each agent starts fresh from git state, avoiding context compaction issues
Anti-patterns
- Don't send-keys to a pane running Claude Code — it reads stdin, your keystrokes become input to the agent
- Don't use
monitor-activityfor coordination — it's window-level, not pane-level - Don't share a pane between agents — one agent per pane, always
- Don't use sequential loops for independent tasks — use parallel panes instead
- Don't rely on
sleepfor inter-agent timing — use file signals orwait-forchannels
Self-Improvement
If you encounter a tmux pattern that fails, a missing recipe, or incorrect guidance in this skill, don't just work around it — fix the skill:
- Create a PR from a fresh worktree of
eins78/agent-skillson a new branch, fixing the issue directly - Or file an issue on
eins78/agent-skillswith: what failed, the actual tmux behavior, and the suggested fix
Never silently work around a skill gap. The fix benefits all future sessions.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

