Stage 4 — Automation
Stage 4 — Automation
Section titled “Stage 4 — Automation”Manual setup is fine when you have one project. With four or five, repeating the same tmux new, Ctrl+a c, rename, split, run-this-here dance every morning gets old fast. This stage automates it: one command per project, idempotent so it doesn’t matter if you’ve already started it, with a project picker menu on top.
🎯 Goal
Section titled “🎯 Goal”After this stage you will be able to:
- Write a startup script that brings up a complete tmux session for a project — windows, panes, running processes — in one command.
- Make scripts idempotent: running them when the session is already up reattaches instead of creating a duplicate.
- Use tmux’s
session:window.panetargeting syntax to address any part of any session from a script. - Maintain a project picker menu that shows all projects with status, ready to attach.
- Decide whether plugins like
tmux-resurrectare worth adding (they usually aren’t, until they are).
📋 Prerequisites
Section titled “📋 Prerequisites”- Stages 1 through 3 complete — you have a working
~/.tmux.confand you’re comfortable with windows, panes, and naming. - Basic shell scripting comfort (
bash,if, variables). - A
~/bindirectory onPATHfor your scripts to live in.
🎬 The pattern: one script per project
Section titled “🎬 The pattern: one script per project”The core idea is a script per project that builds out the session you’d otherwise build by hand. Save it once, run it forever.
Here’s the canonical version for the nafbi project:
#!/bin/bash# Bring up the tmux session for the NAFBI project.
SESSION="nafbi"PROJECT_DIR="$HOME/all-business/nafbi"
# Idempotency: if the session already exists, just attach.if tmux has-session -t "$SESSION" 2>/dev/null; then tmux attach -t "$SESSION" exit 0fi
# Window 1: dev — split into editor + shelltmux new-session -d -s "$SESSION" -n dev -c "$PROJECT_DIR"tmux split-window -h -t "$SESSION:dev" -c "$PROJECT_DIR"tmux select-pane -t "$SESSION:dev.1"
# Window 2: logs — auto-tail syslogtmux new-window -t "$SESSION" -n logs -c "$PROJECT_DIR"tmux send-keys -t "$SESSION:logs" "tail -f /var/log/syslog" C-m
# Window 3: claude — empty shell, ready for `claude`tmux new-window -t "$SESSION" -n claude -c "$PROJECT_DIR"
# Window 4: git — auto-show statustmux new-window -t "$SESSION" -n git -c "$PROJECT_DIR"tmux send-keys -t "$SESSION:git" "git status" C-m
# Land on the dev window and attachtmux select-window -t "$SESSION:dev"tmux attach -t "$SESSION"Run it:
chmod +x ~/bin/start-nafbi.shstart-nafbi.shFirst run: builds the session, attaches you. Second run (after a detach): notices the session exists, just reattaches. Third run from a fresh terminal: same — reattaches.
That’s the pattern. Copy it for start-thrivesend.sh, start-parallelself.sh, start-ras-kruger.sh. Each one knows the project’s directory, its windows, and what to auto-run in each.
🎯 The targeting syntax
Section titled “🎯 The targeting syntax”The argument that keeps appearing is -t "session:window.pane". Read it as:
session_name : window_name_or_index . pane_indexExamples:
nafbi— the session itselfnafbi:dev— windowdevin sessionnafbinafbi:dev.1— pane 1 of windowdevin sessionnafbinafbi:2— window 2 (by index) in sessionnafbinafbi:logs.0— pane 0 of windowlogs(the first pane after a split is.0, the second is.1)
You’ll mostly use session:window (whole window) or session:window.pane (specific pane). The first pane in a window is .0 if pane-base-index is 0, or .1 if it’s 1 (the recommended config in Stage 3 sets it to 1).
🛠️ Useful tmux scripting commands
Section titled “🛠️ Useful tmux scripting commands”The four building blocks you need for almost any startup script:
| Command | What it does |
|---|---|
tmux new-session -d -s NAME -n WIN -c DIR | Create a session detached, with a named first window in a directory |
tmux new-window -t SESSION -n NAME -c DIR | Add a window to an existing session |
tmux split-window -h -t TARGET -c DIR | Split a pane horizontally (-h = side-by-side) or vertically (-v = stacked) |
tmux send-keys -t TARGET "command" C-m | Type a command into a target shell. C-m is Enter. |
The send-keys ... C-m trick is how you get a shell to actually run a command — without C-m it just types the text and waits.
📁 Where to put scripts
Section titled “📁 Where to put scripts”Convention: ~/bin/start-PROJECT.sh. Keep them all in one place so the project picker (next section) can discover them.
Make sure ~/bin is on PATH. In ~/.bashrc:
export PATH="$HOME/bin:$PATH"Then source ~/.bashrc (or open a new shell). After that, start-nafbi.sh works from anywhere.
📋 The project picker
Section titled “📋 The project picker”When you have five or six projects, even remembering which scripts exist gets tedious. A picker menu lists them all and shows running status.
#!/bin/bash# Interactive picker for project tmux sessions.
set -e
# Find all start-*.sh scripts in ~/binmapfile -t scripts < <(find "$HOME/bin" -maxdepth 1 -type f -name 'start-*.sh' | sort)
if [ ${#scripts[@]} -eq 0 ]; then echo "No project scripts found in ~/bin/start-*.sh" exit 1fi
echo "Available projects:"echo
for i in "${!scripts[@]}"; do script="${scripts[$i]}" # Extract the project name from start-PROJECT.sh name="$(basename "$script" .sh)" name="${name#start-}"
# Mark running vs stopped if tmux has-session -t "$name" 2>/dev/null; then status="[running]" else status="[stopped]" fi
printf " %2d. %-20s %s\n" "$((i+1))" "$name" "$status"done
echoread -rp "Select project (number, or q to quit): " choice
if [[ "$choice" == "q" || -z "$choice" ]]; then exit 0fi
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#scripts[@]} )); then echo "Invalid selection." exit 1fi
script="${scripts[$((choice-1))]}"exec "$script"Make it executable:
chmod +x ~/bin/projects.shThen either run projects.sh directly, or alias it to something short — alias p=projects.sh in ~/.bashrc.
The output looks like:
Available projects:
1. nafbi [running] 2. parallelself [stopped] 3. ras-kruger [stopped] 4. thrivesend [running]
Select project (number, or q to quit):Pick a number, the matching start-*.sh runs, and you’re in.
💡 Variations and tips
Section titled “💡 Variations and tips”Auto-run dev servers cautiously. If a window has tmux send-keys ... "npm run dev" C-m, the dev server starts every time you bring the session up. That’s usually what you want — but if a session has been running for hours, restarting the dev server might lose state. For long-lived processes, prefer to start them manually the first time.
Use if to guard expensive setup. Inside the script, after tmux has-session succeeds (session exists), you can also re-run cheap idempotent commands (e.g. git status in a new window) without recreating the session. The simple attach && exit 0 pattern in the example above is the cleanest default.
Watch out for paths that don’t exist. If PROJECT_DIR doesn’t exist on the machine, tmux new-session -c "$PROJECT_DIR" will silently fall back to the home directory. Add a guard:
if [ ! -d "$PROJECT_DIR" ]; then echo "Project directory $PROJECT_DIR not found." exit 1fiDifferent hosts, same script. If a script needs to work on both Mac and the server, use $HOME and conditional paths rather than hardcoded /Users/... or /home/....
🔌 Optional: TPM and plugins
Section titled “🔌 Optional: TPM and plugins”tmux has a plugin manager — TPM, the Tmux Plugin Manager — that lets you install community plugins. Don’t reach for this until the foundation is solid, but two plugins are genuinely useful for the right person.
tmux-resurrect
Section titled “tmux-resurrect”Saves all your sessions, windows, panes, and (optionally) running processes to disk. Lets you restore them across reboots.
| Keystroke | Action |
|---|---|
Ctrl+a Ctrl+s | Save all sessions |
Ctrl+a Ctrl+r | Restore from save |
Useful if you reboot regularly and don’t want to re-run startup scripts. Less useful if you have good startup scripts and rarely reboot.
tmux-continuum
Section titled “tmux-continuum”Pairs with tmux-resurrect. Auto-saves every 15 minutes and can auto-restore on tmux start. Set-and-forget version of resurrect.
Installing TPM and plugins
Section titled “Installing TPM and plugins”# Install TPMgit clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpmAdd to the bottom of ~/.tmux.conf:
# ── Plugins ──────────────────────────────────────────────set -g @plugin 'tmux-plugins/tpm'set -g @plugin 'tmux-plugins/tmux-resurrect'set -g @plugin 'tmux-plugins/tmux-continuum'
# Continuum settingsset -g @continuum-restore 'on'set -g @continuum-save-interval '15'
# Initialize TPM (KEEP THIS LAST IN THE FILE)run '~/.tmux/plugins/tpm/tpm'Reload config (Ctrl+a r), then install plugins:
Ctrl+a I(That’s a capital I, for Install.) TPM downloads the listed plugins and reloads.
✅ Where this leaves you
Section titled “✅ Where this leaves you”If you’ve worked through all four stages, you can:
- Survive on a remote server with no fear of losing work.
- Live in tmux daily for editing, monitoring, and version control.
- Make tmux match your taste with config you understand.
- Spin up an entire project workspace in one command.
That’s everything most people need. Use the Cheat Sheet as your daily reference and the Troubleshooting page when something feels wrong.
➡️ Where to next
Section titled “➡️ Where to next”- Daily reference: Cheat Sheet
- When things break: Troubleshooting
- For Claude Code on the server specifically: Claude Persistent Sessions