home / skills / 0xbigboss / claude-code / tmux
This skill helps you manage long-lived dev processes in tmux by using session-scoped windows and shell initialization.
npx playbooks add skill 0xbigboss/claude-code --skill tmuxReview the files below or copy the command above to add this skill to your agents.
---
name: tmux
description: Patterns for running long-lived processes in tmux. Use when starting dev servers, watchers, tilt, or any process expected to outlive the conversation.
---
# tmux Process Management
## Session Reuse Rules
These are **hard requirements**, not suggestions:
- **MUST** check `tmux has-session` before ever calling `tmux new-session`
- **MUST** derive session name from `git rev-parse --show-toplevel`, never hardcode
- **MUST** add windows to an existing project session, never create a parallel session
- **MUST** use `send-keys` to run commands, never pass inline commands to `new-session`
- **NEVER** create a new session if one already exists for the current project
One project = one tmux session. Multiple processes = multiple windows within that session.
## Interactive Shell Requirement
**Use send-keys pattern for reliable shell initialization.** Creating a session spawns an interactive shell automatically. Use `send-keys` to run commands within that shell, ensuring PATH, direnv, and other initialization runs properly.
```bash
# WRONG - inline command bypasses shell init, breaks PATH/direnv
tmux new-session -d -s "$SESSION" -n main 'tilt up'
# CORRECT - check for session, then use send-keys in interactive shell
if ! tmux has-session -t "$SESSION" 2>/dev/null; then
tmux new-session -d -s "$SESSION" -n main
fi
tmux send-keys -t "$SESSION:main" 'tilt up' Enter
```
## Session Naming Convention
Always derive session name from the project:
```bash
SESSION=$(basename $(git rev-parse --show-toplevel 2>/dev/null) || basename $PWD)
```
For multiple processes in one project, use windows not separate sessions:
- Session: `myapp`
- Windows: `server`, `tests`, `logs`
## Starting Processes
### Single Process
```bash
SESSION=$(basename $(git rev-parse --show-toplevel 2>/dev/null) || basename $PWD)
if ! tmux has-session -t "$SESSION" 2>/dev/null; then
tmux new-session -d -s "$SESSION" -n main
tmux send-keys -t "$SESSION:main" '<command>' Enter
else
echo "Session $SESSION already exists"
fi
```
### Adding a Window to Existing Session
```bash
SESSION=$(basename $(git rev-parse --show-toplevel 2>/dev/null) || basename $PWD)
# Add a new window if it doesn't exist
if ! tmux list-windows -t "$SESSION" -F '#{window_name}' | grep -q "^server$"; then
tmux new-window -t "$SESSION" -n server
tmux send-keys -t "$SESSION:server" 'npm run dev' Enter
else
echo "Window 'server' already exists"
fi
```
### Multiple Processes (Windows)
```bash
SESSION=$(basename $(git rev-parse --show-toplevel 2>/dev/null) || basename $PWD)
# Create session if needed, then add windows
if ! tmux has-session -t "$SESSION" 2>/dev/null; then
tmux new-session -d -s "$SESSION" -n server
tmux send-keys -t "$SESSION:server" 'npm run dev' Enter
fi
# Add more windows (idempotent)
for win in tests logs; do
if ! tmux list-windows -t "$SESSION" -F '#{window_name}' | grep -q "^${win}$"; then
tmux new-window -t "$SESSION" -n "$win"
fi
done
tmux send-keys -t "$SESSION:tests" 'npm run test:watch' Enter
tmux send-keys -t "$SESSION:logs" 'tail -f logs/app.log' Enter
```
## Monitoring Output
```bash
SESSION=$(basename $(git rev-parse --show-toplevel 2>/dev/null) || basename $PWD)
# Last 50 lines from first window
tmux capture-pane -p -t "$SESSION" -S -50
# From specific window
tmux capture-pane -p -t "$SESSION:server" -S -50
# Check for errors
tmux capture-pane -p -t "$SESSION" -S -100 | rg -i "error|fail|exception"
# Check for ready indicators
tmux capture-pane -p -t "$SESSION:server" -S -50 | rg -i "listening|ready|started"
```
## Lifecycle Management
```bash
SESSION=$(basename $(git rev-parse --show-toplevel 2>/dev/null) || basename $PWD)
# List all sessions (see what exists)
tmux ls
# List windows in current session
tmux list-windows -t "$SESSION"
# Kill only this project's session
tmux kill-session -t "$SESSION"
# Kill specific window
tmux kill-window -t "$SESSION:tests"
# Send keys to a window (e.g., Ctrl+C to stop)
tmux send-keys -t "$SESSION:server" C-c
```
## Isolation Rules
- **Never** use `tmux kill-server`
- **Never** kill sessions not matching current project
- **Always** derive session name from git root or pwd
- **Always** verify session name before kill operations
- Other Claude Code instances may have their own sessions running
## When to Use tmux
| Scenario | Use tmux? |
|----------|-----------|
| `tilt up` | Yes, always |
| Dev server (`npm run dev`, `rails s`) | Yes |
| File watcher (`npm run watch`) | Yes |
| Test watcher (`npm run test:watch`) | Yes |
| Database server | Yes |
| One-shot build (`npm run build`) | No |
| Quick command (<10s) | No |
| Need stdout directly in conversation | No |
## Checking Process Status
```bash
SESSION=$(basename $(git rev-parse --show-toplevel 2>/dev/null) || basename $PWD)
# Check session exists
tmux has-session -t "$SESSION" 2>/dev/null && echo "session exists" || echo "no session"
# List windows and their status
tmux list-windows -t "$SESSION" -F '#{window_name}: #{pane_current_command}'
# Check if specific window exists
tmux list-windows -t "$SESSION" -F '#{window_name}' | grep -q "^server$" && echo "server window exists"
```
## Restarting a Process
```bash
SESSION=$(basename $(git rev-parse --show-toplevel 2>/dev/null) || basename $PWD)
# Send Ctrl+C then restart command
tmux send-keys -t "$SESSION:server" C-c
sleep 1
tmux send-keys -t "$SESSION:server" 'npm run dev' Enter
```
## Common Patterns
### Start dev server if not running
```bash
SESSION=$(basename $(git rev-parse --show-toplevel 2>/dev/null) || basename $PWD)
if ! tmux has-session -t "$SESSION" 2>/dev/null; then
tmux new-session -d -s "$SESSION" -n server
tmux send-keys -t "$SESSION:server" 'npm run dev' Enter
echo "Started dev server in tmux session: $SESSION"
elif ! tmux list-windows -t "$SESSION" -F '#{window_name}' | grep -q "^server$"; then
tmux new-window -t "$SESSION" -n server
tmux send-keys -t "$SESSION:server" 'npm run dev' Enter
echo "Added server window to session: $SESSION"
else
echo "Server already running in session: $SESSION"
fi
```
### Wait for server ready
```bash
SESSION=$(basename $(git rev-parse --show-toplevel 2>/dev/null) || basename $PWD)
# Poll for ready message
for i in {1..30}; do
if tmux capture-pane -p -t "$SESSION:server" -S -20 | rg -q "listening|ready"; then
echo "Server ready"
break
fi
sleep 1
done
```
This skill provides patterns and safe defaults for running long-lived processes inside tmux sessions tied to a project. It focuses on idempotent session creation, window management, and reliable shell initialization so dev servers, watchers, and background tools survive the conversation. The guidance prevents accidental destruction of unrelated sessions and ensures environment initialization (PATH, direnv) runs correctly.
The skill derives a session name from the git project root (falling back to the current directory) and enforces a one-project-one-session rule. It always checks for an existing session with tmux has-session and uses send-keys to run commands inside the spawned interactive shell. Windows represent separate processes inside the same session; monitoring and lifecycle operations target the project session only.
Why use send-keys instead of passing commands to new-session?
send-keys runs commands in the interactive shell created by tmux so shell initialization (PATH, direnv, shell rc) runs; inline commands to new-session often bypass that initialization and break environment-dependent tools.
How should I name sessions and windows?
Derive the session name from the git project root (basename of git rev-parse --show-toplevel) and use descriptive window names like server, tests, logs for separate processes.