Skill System Tmux
Route shell commands through tmux when they risk timeout, require persistence, or need interactive terminal access. This skill provides decision heuristics and session management conventions — not tmux tutorials.
Decision Matrix
Use this matrix to decide: direct bash vs tmux.
| Condition | Route | Examples |
|---|---|---|
| Estimated time > 60s | tmux | git clone <large>, npm install, docker build, training scripts |
| Command does not self-terminate | tmux | npm run dev, python app.py, jupyter notebook |
| Requires TUI or interactive input | tmux | vim, htop, pudb, claude |
| Parallel isolated execution needed | tmux | Multiple agents, concurrent test suites |
| Remote execution over SSH | tmux | Commands that must survive connection drops |
| Atomic, non-interactive, fast (<30s) | direct bash | ls, grep, git status, cat, mkdir |
| Single-shot with expected output | direct bash | python script.py (fast), git diff, npm test (short) |
Default: direct bash. Only route to tmux when a condition above is met.
Time Estimation Heuristics
Commands likely to exceed timeout:
- Git operations:
clone(large repos),push(large payloads),checkout(>1GB working tree) - Package install:
npm install,pip install(many deps),cargo build - Build tools:
docker build,make(large projects),webpack(production builds) - Data processing: Training scripts, preprocessing pipelines, large file transfers
- CI/test suites: Full test runs on large projects
Session Naming Convention
Use project-context naming for human readability:
Format: {project}-{purpose}
Agent-created sessions (MANDATORY prefix):
oc-{purpose} # e.g., oc-audit, oc-verify, oc-render
oc-{project}-{task} # e.g., oc-skills-build, oc-subproject-test
User/infra sessions (no prefix required):
postgres-dev
frontend-watch
ml-trainingRules:
- Agent-created sessions MUST use
oc-prefix — enables bulk identification and cleanup - Lowercase, hyphens for separators (not underscores)
- Include project context for multi-project environments
- Reuse well-known
oc-session names when they exist (e.g.,oc-exp-runner) - If you inherit a legacy agent session without the prefix, rename it to
oc-*before reuse - Never create sessions with numeric-only names (e.g.,
531)
Core Patterns
Pattern 1: Fire and Forget (Background Task)
For long-running commands where you don't need real-time output:
# Kill any existing session with same name, then start fresh
tmux kill-session -t {session} 2>/dev/null || true
tmux new-session -d -s {session} bash -c '{command}'Using interactive_bash tool:
tmux_command: new-session -d -s {session}
tmux_command: send-keys -t {session} "{command}" EnterPattern 2: Persistent Server
For dev servers or processes that must keep running:
tmux_command: new-session -d -s {session}
tmux_command: send-keys -t {session} "{command}" EnterCheck if running later:
tmux_command: has-session -t {session}Pattern 3: Output Capture
Read what a tmux session has produced:
tmux_command: capture-pane -p -t {session}For longer history (last 1000 lines):
tmux_command: capture-pane -p -t {session} -S -1000Pattern 4: Readiness Detection
After sending a command, poll to detect completion:
- Capture the pane output
- Look for shell prompt at end of output (regex:
/(\\$|>|#)\s*$/m) - If prompt appears, command has finished
- If no prompt after expected duration, assume still running or hung
Pattern 5: Clean Shutdown
Always clean up sessions when done:
tmux_command: kill-session -t {session}Kill-before-new pattern prevents zombie sessions:
tmux_command: kill-session -t {session}
# (ignore error if doesn't exist)
tmux_command: new-session -d -s {session}Pattern 6: Bulk Resource Reclaim
Reclaim all agent-created sessions and detect stale resources. Run this when:
- Machine feels sluggish or memory is high
- Before/after long agent work sessions
- User requests cleanup
Step 1: Identify agent sessions
tmux list-sessions -F '#{session_name} #{session_activity}' | grep '^oc-'Step 2: Kill all agent sessions
tmux list-sessions -F '#{session_name}' | grep '^oc-' | xargs -I{} tmux kill-session -t {}Step 3: Detect stale non-agent sessions
A session is likely stale if:
- Last activity > 6 hours ago AND no running foreground process
- Session has only an idle shell prompt (capture-pane shows just
$or╰─)
# List sessions with last activity timestamp
tmux list-sessions -F '#{session_name} #{session_activity}'
# Compare #{session_activity} (epoch) against current time
# Capture pane of suspects to verify idle stateStep 4: Reclaim orphaned processes
# Find opencode processes whose parent tmux pane was killed
ps aux | grep opencode | grep -v grep
# Check if port is held by a zombie
lsof -i :{port} 2>/dev/null
# Force kill if needed (SIGTERM first, SIGKILL if unresponsive after 5s)
kill {pid} && sleep 5 && kill -0 {pid} 2>/dev/null && kill -9 {pid}Important: Never kill known non-agent infra sessions without explicit user confirmation. Legacy names from sibling projects should be normalized to oc-* before they are treated as agent-managed sessions.
Integration with Agent Tools
interactive_bash Tool
The primary interface. Pass tmux subcommands directly (without tmux prefix):
# Create session
tmux_command: new-session -d -s oc-my-session
# Send command
tmux_command: send-keys -t oc-my-session "npm run dev" Enter
# Read output
tmux_command: capture-pane -p -t oc-my-session
# Kill session
tmux_command: kill-session -t oc-my-sessionbash Tool with Timeout
For commands near the timeout boundary, prefer increasing timeout parameter over tmux:
bash(command="npm install", timeout=300000) # 5 min timeoutUse tmux only when:
- Timeout cannot be reliably estimated
- Process must persist beyond the current tool call
- Interactive/TUI access is needed
Known Errors & Workarounds
capture-pane blocked in interactive_bash
Error: 'capture-pane' is blocked in interactive_bash
This is the most common tmux error. The interactive_bash tool blocks certain tmux subcommands (including capture-pane, pipe-pane, save-buffer). Always use the Bash tool instead for these operations.
# WRONG — will be blocked:
# interactive_bash: tmux_command: capture-pane -p -t oc-dev-server
# CORRECT — use Bash tool:
tmux capture-pane -p -t oc-dev-server
# Capture with history (last 1000 lines):
tmux capture-pane -p -t oc-dev-server -S -1000
# Capture and grep for specific output:
tmux capture-pane -p -t oc-dev-server | grep -i "error\|ready\|listening"Rule: For any tmux read-only operation (capture-pane, list-sessions, display-message, show-options), prefer the Bash tool. Reserve interactive_bash for mutations (send-keys, new-session, kill-session).
session not found after kill/recreate
Error: can't find session: oc-xxx
Common when kill-before-new pattern races. Add a small guard:
tmux kill-session -t oc-build 2>/dev/null || true
tmux new-session -d -s oc-build bash -c 'make all'duplicate session on new-session
Error: duplicate session: oc-xxx
The session already exists. Either reuse it or kill-before-new:
# Reuse (send new command to existing session):
tmux send-keys -t oc-build "make clean && make all" Enter
# Or kill and recreate:
tmux kill-session -t oc-build 2>/dev/null || true
tmux new-session -d -s oc-build bash -c 'make all'no server running / tmux not started
Error: no server running on /tmp/tmux-*/default
No tmux server is running. Any new-session command will start one automatically:
tmux new-session -d -s oc-init echo "tmux ready"Tool Selection Quick Reference
| tmux subcommand | Use interactive_bash? | Use Bash tool? |
|---|---|---|
new-session | Yes | Yes |
send-keys | Yes (preferred) | Yes |
kill-session | Yes | Yes |
capture-pane | NO — blocked | Yes (required) |
list-sessions | Avoid | Yes (preferred) |
has-session | Yes | Yes |
pipe-pane | NO — blocked | Yes (required) |
display-message | Avoid | Yes (preferred) |
Anti-Patterns
| Don't | Do Instead |
|---|---|
| Route every command through tmux | Use direct bash for fast, atomic commands |
| Forget to kill sessions after use | Always clean up with kill-session |
Use generic session names like s1 | Use descriptive names: oc-project-purpose |
Create agent sessions without oc- prefix | Always prefix: oc-audit, oc-verify, oc-build |
| Poll capture-pane in tight loops | Use 1-2s intervals between polls |
| Start a new session without killing old one | Always kill-before-new for same session name |
| Send commands without clearing the line first | Send C-u before commands if line might have residual input |
| Leave sessions running after task completes | Run bulk reclaim (Pattern 6) at session end |
Use interactive_bash for capture-pane | Always use Bash tool for capture-pane |
Retry blocked commands in interactive_bash | Switch to Bash tool immediately |
Existing Project Conventions
This skill system's sibling projects may contain older tmux naming patterns, but agent-owned sessions in this repo must be normalized to oc-*.
- Session name
oc-exp-runner: Preferred agent-owned name for experiment execution - Watcher sessions: Background monitors that wake agents via
tmux send-keys machines.jsonconfig:tmux_sessionfield defines per-machine session names- Kill-before-new: Standard cleanup pattern used across all experiment scripts
When working in a project with existing tmux conventions, preserve true user/infra sessions, but rename inherited agent sessions to the oc-* form before continuing.
{
"schema_version": "2.0",
"id": "skill-system-tmux",
"version": "1.0.0",
"capabilities": ["tmux-session-start", "tmux-capture", "tmux-wait", "tmux-session-list", "tmux-session-kill"],
"effects": ["proc.exec"],
"operations": {
"start-session": {
"description": "Start a long-running command in a managed tmux session.",
"input": {
"name": { "type": "string", "required": true, "description": "Logical session name" },
"command": { "type": "string", "required": true, "description": "Command to execute inside tmux" }
},
"output": {
"description": "Started session metadata",
"fields": { "status": "string", "session_name": "string" }
},
"entrypoints": {
"unix": ["python3", "{skill_dir}/scripts/tmux_tool.py", "start-session", "{name}", "{command}"]
}
},
"capture-pane": {
"description": "Capture output from a managed tmux session.",
"input": {
"session_name": { "type": "string", "required": true, "description": "Target session name" },
"lines": { "type": "integer", "required": false, "description": "Number of lines to capture" }
},
"output": {
"description": "Captured output",
"fields": { "status": "string", "output": "string" }
},
"entrypoints": {
"unix": ["python3", "{skill_dir}/scripts/tmux_tool.py", "capture-pane", "{session_name}"]
}
},
"wait-completion": {
"description": "Poll a managed tmux session until completion.",
"input": {
"session_name": { "type": "string", "required": true, "description": "Target session name" },
"timeout_seconds": { "type": "integer", "required": false, "description": "Maximum wait time" }
},
"output": {
"description": "Completion result and final output",
"fields": { "status": "string", "completed": "boolean", "output": "string" }
},
"entrypoints": {
"unix": ["python3", "{skill_dir}/scripts/tmux_tool.py", "wait-completion", "{session_name}"]
}
},
"list-sessions": {
"description": "List managed tmux sessions.",
"input": {},
"output": {
"description": "Managed session rows",
"fields": { "status": "string", "sessions": "array" }
},
"entrypoints": {
"unix": ["python3", "{skill_dir}/scripts/tmux_tool.py", "list-sessions"]
}
},
"kill-session": {
"description": "Kill a managed tmux session.",
"input": {
"session_name": { "type": "string", "required": true, "description": "Target session name" }
},
"output": {
"description": "Kill result",
"fields": { "status": "string", "session_name": "string" }
},
"entrypoints": {
"unix": ["python3", "{skill_dir}/scripts/tmux_tool.py", "kill-session", "{session_name}"]
}
}
},
"stdout_contract": {
"last_line_json": false,
"note": "Agent-executed; uses interactive_bash tool for tmux operations."
}
}