skills/tmux-spawn/SKILL.md
Reliable agent spawning in tmux with load-wait and verification
npx skillsauth add Cheggin/skill-chain tmux-spawnInstall 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.
Encapsulates the correct pattern for spawning Claude Code agents in tmux panes. Every spawn in the harness MUST follow this 5-step protocol.
Naive tmux spawning fails silently in multiple ways:
.zshrc/.bashrc are not sourced, so claude, lfg, nvm, etc. are not on PATH.tmux send-keys "text" Enter appends the literal string "Enter" to the text instead of pressing the Enter key. Text and Enter MUST be separate send-keys calls.setTimeout in a CLI process to delay sending keys causes the process to exit before the timer fires. Must use synchronous sleep.All agents share ONE tmux window named agents (by default). New spawns split that window and select-layout tiled keeps every pane equally sized in an alternating vertical/horizontal grid. You never have to flip between windows to watch the fleet.
WIN=agents
SESSION=harness
# Does the window exist?
if ! tmux list-windows -t "$SESSION" 2>/dev/null | grep -qE "^[0-9]+: $WIN[\* ]"; then
# First agent: create the window and the first pane. Name the pane via
# @title so capture/target calls can find it.
tmux new-window -t "$SESSION" -n "$WIN" \
"/bin/zsh -lc '[ -f ~/.zshrc ] && . ~/.zshrc; cd $REPO && claude --dangerously-skip-permissions --model $MODEL'"
tmux select-pane -t "$SESSION:$WIN" -T "$AGENT_NAME"
else
# Subsequent agents: split the most recent pane in the same window,
# then re-tile so every pane is equally sized.
tmux split-window -t "$SESSION:$WIN" \
"/bin/zsh -lc '[ -f ~/.zshrc ] && . ~/.zshrc; cd $REPO && claude --dangerously-skip-permissions --model $MODEL'"
tmux select-pane -T "$AGENT_NAME"
tmux select-layout -t "$SESSION:$WIN" tiled
fi
All subsequent commands target the pane by its title (set in Step 1):
PANE=$(tmux list-panes -t "$SESSION:$WIN" -F '#{pane_title}:#{pane_id}' | grep "^$AGENT_NAME:" | head -1 | cut -d: -f2)
Poll capture-pane on that specific pane for ready indicators (>, claude>, Claude Code, Tips:).
until tmux capture-pane -t "$PANE" -p -S -10 2>/dev/null | grep -qE '>|claude>|Claude Code|Tips:'; do
sleep 2
done
The waitForReady() function handles this with configurable timeout.
tmux send-keys -t "$PANE" 'your prompt text here'
tmux send-keys -t "$PANE" Enter
The sendKeys() function does steps 4+5 automatically.
After a brief pause (3s), capture pane output and check for activity indicators. Then re-tile once in case the capture broadcast changed pane sizing.
sleep 3
tmux capture-pane -t "$PANE" -p -S -15 2>/dev/null
tmux select-layout -t "$SESSION:$WIN" tiled
# Check for: Read, Edit, Bash, Grep, thinking, searching
# Warn if: Tips:, Available commands:
tiled (not manual -h / -v splits)tmux select-layout tiled alternates vertical and horizontal splits automatically and resizes every pane equally. You get a 1x1 → 1x2 → 2x2 → 2x3 → 3x3 progression without having to pick split direction per agent. Manually alternating -h / -v while targeting specific panes produces slivers and gets out of sync with agents that exit.
tmux new-window per agent — produces N separate windows you have to tab through. Use split-window on the shared window instead.tmux split-window -h without select-layout tiled — each new agent gets skinnier than the last. Re-tile after every split.$WIN.1, $WIN.2) — indices shift when panes close. Use @title + #{pane_title} lookup.agents-1, agents-2) — defeats the one-window requirement. One window, N panes.All functions are exported from packages/cli/src/lib/tmux.ts:
| Function | Purpose |
|---|---|
| spawnPane(name, command) | Spawns a tmux window with login shell wrapping |
| sendKeys(name, text) | Sends text + Enter as separate calls |
| waitForReady(name, timeoutMs?, pollMs?) | Polls for Claude Code ready state |
| verifyRunning(name, waitMs?) | Checks for agent activity after prompt |
| sleepSync(ms) | Synchronous sleep (safe in CLI context) |
| wrapWithLoginShell(command) | Internal: wraps command for .zshrc sourcing |
harness agent spawn <name> <prompt>)spawnPane(name, "cd /repo && claude --dangerously-skip-permissions --model ...")
-> waitForReady(name)
-> sendKeys(name, prompt)
-> verifyRunning(name)
harness loop start <name>)spawnPane(paneName, "cd /repo && claude --dangerously-skip-permissions --model ...")
-> waitForReady(paneName)
-> sendKeys(paneName, "/loop 5m <prompt>")
-> verifyRunning(paneName)
| Symptom | Cause | Fix |
|---|---|---|
| claude: command not found | Non-login shell, PATH not set | wrapWithLoginShell() |
| Prompt visible but not submitted | Enter sent with text, not separately | sendKeys() two-step |
| Prompt sent but ignored | Claude Code not loaded yet | waitForReady() |
| No error but agent idle | setTimeout in CLI, process exited | sleepSync() |
development
Design engineering principles for making interfaces feel polished. Use when building UI components, reviewing frontend code, implementing animations, hover states, shadows, borders, typography, micro-interactions, enter/exit animations, or any visual detail work. Triggers on UI polish, design details, "make it feel better", "feels off", stagger animations, border radius, optical alignment, font smoothing, tabular numbers, image outlines, box shadows.
documentation
Agentic memory system for writers - track characters, relationships, scenes, and themes
documentation
LLM Wiki — persistent markdown knowledge base that compounds across sessions (Karpathy model)
development
Build production-quality SaaS websites with opinionated design presets. Use when creating any startup website. The user MUST pick a design style before building. Enforces shadcn/ui, Figma design principles, specific CSS values per style, and anti-AI-writing. Load alongside anti-ai-writing skill. LIGHT MODE ONLY.