Tmux + Git worktree toolkit for parallel Claude Code sessions
A toolkit that ties together tmux, Git worktrees, and Claude Code so you can run multiple Claude Code agents in parallel — each on its own branch, in its own isolated environment — and switch between them instantly.
Works locally or on a remote dev box. The remote setup is arguably the better experience — sessions persist across disconnects, your laptop stays cool, and Claude Code agents keep working while your lid is closed.
When you work with Claude Code on a project, you sometimes want to:
- Work on a feature while Claude Code handles a refactor on a different branch
- Context-switch to an urgent fix without losing your current Claude Code conversation
- Run multiple Claude Code agents simultaneously without them stepping on each other's files
- Kick off a long task, close your laptop, and come back to finished work
Git worktrees give you multiple checked-out branches at once. Tmux gives you persistent terminal sessions you can hop between. This toolkit wires them together so the workflow feels like switching browser tabs.
Good for: single-machine workflows, quick setup, no network dependency.
Use install.sh — installs the shell functions and tmux config. You manage prerequisites yourself.
Good for: always-on agents, keeping your laptop light, consistent Linux environment, SSH-from-anywhere access.
Use provision.sh — fully automated setup of an Ubuntu server with everything installed and configured. Your laptop becomes a thin client.
Both setups use the same cc commands and tmux keybindings. The only difference is where the sessions run.
| Tool | What it does | Install |
|---|---|---|
| tmux | Terminal multiplexer — lets you run multiple terminal sessions | brew install tmux (macOS) or apt install tmux (Linux) |
| fzf | Fuzzy finder — powers the interactive pickers | brew install fzf or apt install fzf |
| git | Version control (you already have this) | — |
| claude | Claude Code CLI | npm install -g @anthropic-ai/claude-code |
cd cc-workflow
chmod +x install.sh
./install.shThe installer will:
- Copy shell functions to
~/.config/cc-workflow/ - Install the tmux config to
~/.tmux.conf(backs up any existing one) - Install tmux plugin manager (tpm) if needed
- Add a
sourceline to your.bashrcor.zshrc
After installing, reload your shell:
source ~/.bashrc # or source ~/.zshrcThen install the tmux plugins by opening tmux and pressing Ctrl-b I (capital I). You'll see a message when it's done.
Running this on a dedicated server changes the workflow in meaningful ways:
Sessions survive everything on your end. Close your laptop, switch networks, reboot — SSH back in and you're exactly where you left it. Locally, tmux protects you from accidentally closing a terminal. Remotely, it protects you from real life.
Always-on agents. Kick off ccn feature-auth, give Claude Code a complex task, then disconnect. Next morning, SSH in and the work is done (or waiting for your input). Locally, your machine has to stay awake and running.
Dedicated resources. Multiple Claude Code instances can be CPU and memory hungry. Offloading to a server keeps your laptop responsive. You're treating your laptop as a thin client.
Consistent environment. If you deploy to Linux, developing on Linux removes an entire class of "works on my machine" surprises. File paths, case sensitivity, line endings — all match production.
- A fresh Ubuntu 22.04+ server (Hetzner, DigitalOcean, AWS, a box in your closet — anything works)
- SSH access with a non-root sudo user
- A terminal that supports OSC 52 for clipboard passthrough (Ghostty, WezTerm, iTerm2, kitty — most modern terminals do)
A 4-core / 8 GB RAM / SSD instance is plenty for several parallel Claude Code sessions. On Hetzner, that's a CPX31 at roughly €15/month.
The provisioning script sets up everything from bare Ubuntu to a fully configured dev environment:
# Option 1: pipe directly over SSH
ssh your-server 'bash -s' < provision.sh
# Option 2: copy and run
scp provision.sh your-server:
ssh your-server ./provision.shThe script installs and configures:
- System packages — tmux, fzf, mosh, git, ripgrep, build-essential, jq, htop
- Node.js — via nvm, with the version pinned to LTS (currently v22)
- Claude Code CLI — globally via npm
- cc-workflow toolkit — shell functions and tmux config (remote-optimized with OSC 52 clipboard)
- SSH server tuning — keepalive intervals so long sessions don't get killed
- Mosh firewall rules — opens UDP 60000–61000 if ufw is active
- tmux auto-attach — SSH login drops you straight into tmux
- tmux plugin manager — for session persistence across reboots
The script is idempotent — safe to run multiple times. It checks the state of everything before making changes:
- Packages are only installed if missing (skips
apt-get updateentirely on re-run) - Config files are only written if their content has changed
- Shell rc lines are only appended if the marker string isn't already present
- Git config values are only set if currently unset (won't overwrite your customizations)
- Firewall rules are only added if not already present
- sshd is only reloaded if its config actually changed
A clean second run produces all "no change" messages with no sudo prompts and no network calls.
After the script completes, it prints the remaining manual steps:
1. Reconnect to activate tmux auto-attach:
exit
ssh your-server # you'll land directly in tmux2. Install tmux plugins (one time, from inside tmux):
Press Ctrl-b then Shift-I. Wait for the confirmation message.
3. Authenticate Claude Code:
claude authThis opens a browser-based OAuth flow. If you're on a headless server, Claude Code will give you a URL to visit on your laptop.
4. Clone a project and start working:
cd ~/projects
git clone git@github.com:your-org/your-repo.git
cd your-repo
ccn feature-somethingAdd an entry to ~/.ssh/config on your local machine:
Host dev
HostName <your-server-ip>
User <your-username>
ServerAliveInterval 30
ServerAliveCountMax 5
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
Create the socket directory:
mkdir -p ~/.ssh/socketsNow you have two ways to connect:
ssh dev # standard SSH — auto-attaches to tmux
mosh dev # resilient connection (recommended)Use mosh for daily work. It handles network changes gracefully — switching from home WiFi to a coffee shop, or your laptop sleeping and waking. Where SSH would hang and require reconnection, mosh recovers silently. The one limitation is that mosh doesn't support scrollback, but that doesn't matter here because tmux handles all your scrolling.
The ControlMaster and ControlPersist settings in the SSH config multiplex connections. After your first SSH connection, subsequent ones (like scp or git push over SSH) reuse the existing tunnel and connect instantly.
The remote tmux config uses OSC 52 escape sequences to pass copied text from the server through to your local clipboard. When you yank text in tmux copy mode (prefix + v to enter, select text, y to yank), it arrives on your local clipboard ready to paste — even though the text lives on a remote machine.
For this to work, your terminal must support OSC 52:
| Terminal | Status | Configuration needed |
|---|---|---|
| Ghostty | Works out of the box | None |
| WezTerm | Works out of the box | None |
| kitty | Works out of the box | None |
| iTerm2 | Needs a setting | Preferences → General → Selection → "Applications in terminal may access clipboard" |
| macOS Terminal | Not supported | Switch to one of the above |
| Windows Terminal | Works out of the box | None |
If clipboard passthrough isn't working, make sure you're connecting via SSH (not mosh — mosh can interfere with OSC 52 on some setups). For mosh, you may need to set mosh-server to pass through the sequences, or use SSH for the occasional copy operation.
If you've never used tmux, here's the 2-minute version.
Tmux runs in the background and manages sessions. Each session is like an independent desktop with its own terminal windows. When you close your terminal or disconnect from SSH, your sessions keep running. You reconnect to them later and everything is exactly where you left it.
In this workflow, each session maps to one Git branch running one Claude Code instance.
Almost every tmux command starts by pressing Ctrl-b, then releasing both keys, then pressing another key. This two-step pattern is called the prefix.
Throughout this doc, prefix means press Ctrl-b and release it.
For example, "press prefix + d" means:
- Hold
Ctrl, pressb, release both - Press
d
These are the only tmux commands you need to memorize on day one:
| What you want to do | How |
|---|---|
| Detach from tmux (leave it running in background) | prefix + d |
| Reattach to tmux from a regular terminal | Type tmux attach in your terminal |
| Scroll up to read Claude Code's output | Mouse wheel, or prefix + v then arrow keys. Press q to exit scroll mode. |
| Zoom a pane to full screen (and back) | prefix + z |
That's it. Everything else in this workflow is handled by the cc commands and keybindings described below.
These workflows are identical whether you're running locally or on a remote box. If remote, just ssh dev or mosh dev first — you'll land in tmux automatically.
Navigate to your project repo, then:
ccn feature-authThis single command:
- Creates a Git worktree for a new
feature-authbranch (based onmain) - Creates a tmux session named
feature-auth - Launches Claude Code inside that session
- Switches you into the session
You're now in Claude Code, on an isolated copy of your repo, on its own branch. Start working.
To branch off something other than main:
ccn feature-auth developWhile Claude Code is working on feature-auth, you get pulled into a bug. Without closing or interrupting anything:
ccn fix-billingYou're now in a separate session with its own Claude Code instance working on fix-billing. The feature-auth session is still running in the background — Claude Code's conversation, your shell history, everything.
You have several ways to switch, depending on the situation:
| Method | When to use it |
|---|---|
| Alt+] / Alt+[ | Step forward/backward through sessions. Fastest — no prefix key needed. Good when you have 2–3 sessions and want to flip between them. |
| prefix + L | Toggle between the last two sessions you were in. Perfect for "I'm going back and forth between these two branches." |
| prefix + w | Opens a fuzzy finder showing all sessions. Best when you have several sessions and want to jump to a specific one by name. |
| ccs | Same fuzzy finder, but from a shell prompt instead of inside tmux. Shows a preview of each session's last 20 lines of output. |
The most common pattern: Alt+] to step through, prefix + L to flip between two.
ccstatusShows all worktrees and whether they have an active tmux session:
WORKTREE SESSIONS
─────────────────
SESSION STATUS PATH
------- ------ ----
main ○ no session /home/you/projects/myapp
feature-auth ● active /home/you/projects/myapp-feature-auth
fix-billing ● active /home/you/projects/myapp-fix-billing
Total active sessions: 2
ccrmA fuzzy picker shows your worktrees. Select one, confirm with y, and it kills the tmux session and removes the worktree. Make sure you've merged or pushed the branch first — the worktree's local files are deleted.
Or remove a specific one directly:
ccrm feature-authJust close your terminal or press prefix + d to detach. Everything keeps running. Next time you SSH in, you're back where you were.
For long-running Claude Code tasks, this is the killer feature: start the work, disconnect, come back later.
All commands start with cc for easy tab completion.
| Command | Description |
|---|---|
ccn <branch> [base] |
New — Create worktree + tmux session + launch Claude Code. Base branch defaults to main. |
ccs |
Switch — Fuzzy-pick a session to jump to. Shows a preview of each session's recent output. |
ccl |
List — Show all worktrees and their session status. |
ccrm [branch] |
Remove — Kill session + remove worktree. Prompts for confirmation. Uses fuzzy picker if no branch specified. |
ccj |
Jump — cd into a worktree directory without creating/switching tmux sessions. Handy for quick file checks. |
cca [layout] |
Arrange — Split the current session into a pane layout (see Pane Layouts below). |
ccall <command> |
All — Send a shell command to every active session. Useful for ccall "git pull origin main". |
ccstatus |
Status — Overview of all worktrees and sessions. |
| Binding | Action |
|---|---|
Alt + ] |
Next session (no prefix needed) |
Alt + [ |
Previous session (no prefix needed) |
prefix + L |
Toggle between last two sessions |
prefix + w |
Fuzzy session picker |
prefix + W |
Built-in tree view of all sessions/windows |
prefix + N |
Create a new worktree session (prompts for branch name) |
| Binding | Action |
|---|---|
prefix + | |
Split pane vertically (side by side) |
prefix + - |
Split pane horizontally (top/bottom) |
prefix + h/j/k/l |
Navigate between panes (vim-style: left/down/up/right) |
prefix + H/J/K/L |
Resize panes (repeatable — hold prefix and tap repeatedly) |
prefix + z |
Toggle zoom on current pane (full screen ↔ normal) |
prefix + S |
Toggle synchronized panes (type in all panes at once) |
| Binding | Action |
|---|---|
| Mouse wheel | Scroll up/down through output |
prefix + v |
Enter copy/scroll mode (navigate with arrow keys or vim keys) |
v (in copy mode) |
Start text selection |
y (in copy mode) |
Copy selection to clipboard (works over SSH via OSC 52) |
q (in copy mode) |
Exit copy mode |
/ (in copy mode) |
Search forward through output |
? (in copy mode) |
Search backward through output |
| Binding | Action |
|---|---|
prefix + d |
Detach from tmux (sessions keep running) |
prefix + c |
Create a new window in current session |
Use cca to split your Claude Code session into a multi-pane layout. Run this from a shell pane within your tmux session.
┌──────────────────────┬───────────────┐
│ │ logs / tests │
│ Claude Code │ │
│ ├───────────────┤
│ │ shell / git │
└──────────────────────┴───────────────┘
Best for active development. Watch test output or tail logs in the right panes while Claude Code works on the left.
┌──────────────────────────────────────┐
│ Claude Code │
├──────────────────┬───────────────────┤
│ logs │ shell │
└──────────────────┴───────────────────┘
Good for wide monitors. Gives Claude Code more horizontal space.
┌──────────────────────────────────────┐
│ Claude Code │
├──────────────────────────────────────┤
│ shell │
└──────────────────────────────────────┘
Just Claude Code and a shell. Use when you want to keep things simple.
Tip: Press prefix + z to temporarily zoom any pane to full screen. Press it again to restore the layout. This is great when Claude Code produces long output you want to read without squinting.
By default, worktrees are created as sibling directories to your main repo:
~/projects/
├── myapp/ ← main repo (main branch)
├── myapp-feature-auth/ ← worktree (feature-auth branch)
├── myapp-fix-billing/ ← worktree (fix-billing branch)
Each directory is a fully independent checkout of your repo at a specific branch. Changes in one don't affect the others until you merge.
To use a subdirectory inside the repo instead (.worktrees/), set this in your shell rc before the source line:
export CC_WORKTREE_DIR_STYLE="subdirectory"If your project uses Node.js, each worktree needs its own node_modules. The ccn command detects package.json and kicks off npm install in the background automatically. If you use pnpm, its content-addressable store means multiple worktrees share the actual package data on disk, so this is basically free.
The tmux configuration includes tmux-resurrect and tmux-continuum plugins which automatically save your session layouts every 10 minutes and restore them if the machine restarts.
This means:
- Your pane layouts survive reboots
- Your session names and working directories are preserved
- You'll need to restart Claude Code instances manually after a reboot (the process itself isn't preserved, just the session structure)
To install the plugins after initial setup, open tmux and press prefix + I (capital I).
On a remote box, reboots are rare, so this is mostly insurance. Locally, it saves you from OS updates that force a restart.
Start with 2–3. Each Claude Code instance holds a conversation in memory and may run builds or tests. On a 4-core / 8 GB machine, 3–4 active sessions is comfortable. On an 8-core / 16 GB box, you can comfortably run 5–6.
If you're just parking a session to come back to (Claude Code idle, waiting for your input), it costs almost nothing. The limit is active, working Claude Code instances.
Use descriptive, short branch names. They become your tmux session names and show in the status bar, so fix-billing is better than jsmith/JIRA-4521-fix-billing-edge-case-q3. You'll be reading these at a glance.
ccrm deletes the worktree directory. If you haven't pushed your branch, that work is gone. Make it a habit: push, merge (or open a PR), then ccrm. If you use Claude Code's /git commands, ask it to push before you tear down.
Don't create worktree sessions off a dirty main branch. Pull before branching:
cd ~/projects/myapp # your main checkout
git pull origin main
ccn feature-new-thingOr pull into all worktrees at once:
ccall "git pull origin main"Each worktree is its own directory, so each can have its own CLAUDE.md. Keep these focused on the specific task rather than dumping everything in. A CLAUDE.md that says "You are working on the billing refactor. The relevant files are in src/billing/. Do not modify the API contract." is more effective than a kitchen-sink file.
The provisioning script doesn't set up unattended upgrades. For a dev box, running updates manually is fine:
sudo apt update && sudo apt upgrade -yDo this periodically, especially for security patches. Your tmux sessions will survive the package upgrades (just not a kernel reboot).
Make sure your server uses SSH key authentication and has password auth disabled. The provisioning script doesn't touch authentication settings — it assumes you've already set this up when you created the server. If you haven't:
# On the server
sudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl reload sshEach worktree is a full checkout (minus .git which is shared). A repo with a large working tree — lots of assets, generated files, or node_modules — can add up. Monitor with df -h and clean up old worktrees you're done with.
The workflow is per-repo. If you work across multiple repos, clone them all into ~/projects/ and cd into whichever one before using ccn. Worktrees and sessions are scoped to the repo you're in.
~/projects/
├── frontend/ ← one repo
├── frontend-feature-x/ ← its worktree
├── backend/ ← another repo
├── backend-fix-api/ ← its worktree
Sessions from different repos coexist in tmux without interference. ccstatus only shows worktrees for the repo you're currently in, but prefix + w and Alt+]/Alt+[ cycle through all sessions regardless of repo.
You left three sessions running overnight. Reconnect:
tmux attachYou're back in the last session you were in. Use Alt+]/Alt+[ or prefix + w to check on the others.
mosh devYou're back. Everything is exactly where you left it — even if your laptop rebooted overnight.
mosh dev
cd ~/projects/myapp
ccn refactor-database
# Give Claude Code a detailed task description
# Detach: prefix + d (or just close your terminal)
# Come back later:
mosh dev
# Alt+] to find the refactor-database sessionccall "git pull origin main"You want to glance at a file in another worktree but don't want to switch context:
ccj # fuzzy-pick a worktree, cd into it
cat src/config.ts
cd - # go back to where you werecca dev # set up the pane layout
# Click/navigate to the right-top pane, then:
npm run test:watch
# Click back to the Claude Code pane (or prefix + h)You've tweaked provision.sh and want to apply changes:
ssh dev 'bash -s' < provision.shBecause it's idempotent, it only touches what's changed. No need to worry about duplicating config lines or reinstalling packages.
tmux kill-serverThis destroys all sessions. Use only when you want a clean slate. Your worktrees will still exist on disk — use git worktree list and git worktree remove to clean those up manually.
"not in a git repository"
You need to be inside a Git repo to use ccn, ccl, ccrm, etc. cd into your project first.
"fzf: command not found"
Install fzf: brew install fzf (macOS) or apt install fzf (Linux). The fuzzy pickers (ccs, ccrm, ccj, prefix + w) require it. On a provisioned remote box, fzf is already installed.
Session already exists
If ccn says a session already exists, it'll switch you to the existing one instead of creating a duplicate.
Worktree already exists
If the branch already has a worktree on disk, ccn will reuse it and just create the tmux session.
Alt+] / Alt+[ not working Some terminal emulators capture Alt key combinations. Check your terminal's settings for "meta key" or "option as meta" behavior. In iTerm2, go to Profiles → Keys and set "Left Option Key" to "Esc+". In macOS Terminal, it's Preferences → Profiles → Keyboard → "Use Option as Meta key." Ghostty and WezTerm handle this correctly by default.
Copy to clipboard not working (local)
The local config uses pbcopy (macOS). On Linux, change pbcopy in ~/.tmux.conf to xclip -selection clipboard or xsel --clipboard.
Copy to clipboard not working (remote) The remote config uses OSC 52 escape sequences. Make sure your terminal supports it (see the Clipboard over SSH table above). In iTerm2, you need to explicitly enable clipboard access for terminal applications. If using mosh, try connecting via plain SSH for copy operations — mosh can interfere with OSC 52 depending on your version.
SSH connection drops during long sessions
The provisioning script configures ClientAliveInterval 30 on the server. You should also have ServerAliveInterval 30 in your local ~/.ssh/config (included in the recommended config above). If connections still drop, your network may be aggressively killing idle TCP connections — switch to mosh, which uses UDP and handles this natively.
Pane navigation feels wrong
The config uses vim-style h/j/k/l for pane movement. If you don't use vim, you can also click panes with the mouse (mouse support is enabled) or use the arrow keys with prefix + arrow.
tmux-resurrect didn't restore my sessions
Resurrect saves layouts and working directories, not running processes. After a reboot, your sessions will exist with the right pane layouts, but you'll need to restart Claude Code in each one. Just Alt+] through your sessions and type claude in each.
provision.sh fails on "sshd_config.d"
Older Ubuntu versions (before 22.04) may not have the /etc/ssh/sshd_config.d/ include directory. Either upgrade to 22.04+ or manually add ClientAliveInterval 30 and ClientAliveCountMax 10 to /etc/ssh/sshd_config.
| File | Purpose |
|---|---|
install.sh |
Local installer — copies files and configures shell. For your laptop/desktop. |
provision.sh |
Remote provisioner — full server setup from bare Ubuntu. Idempotent. |
cc-worktrees.sh |
Shell functions (ccn, ccs, ccrm, etc.). Sourced by your shell rc. |
tmux.conf |
Tmux configuration. Local version uses pbcopy, remote version uses OSC 52. |
README.md |
This file. |