Claude Code in tmux background sessions: a working setup
After running between three and twelve concurrent Claude Code agents on the same machine for the last six months, I've collapsed the setup down to one rule: every long-running agent lives in a detached tmux session named after its job. Nothing else.
This article documents the working configuration. It's not the slickest setup on the internet. It's the one that has survived restarts, OS updates, and a few late-night dispatching binges without losing state.
The friction this solves
Three problems show up the moment you try to run more than one Claude Code session:
Sessions die when you close the terminal. A worker mid-build, killed by a window close. Your spec gets half-applied. The commit that would have saved the work never happens.
You can't see what they're doing without context-switching. Two agents, one terminal: now you're tabbing between iTerm windows trying to remember which one is the worker and which one is the manager.
Background bash jobs (run_in_background: true) get SIGTERMed. I learned this the hard way. The harness aggressively cleans up backgrounded shell jobs at task boundaries. A long-running dev server backgrounded that way will die at the next tool call. tmux sessions don't.
tmux fixes all three by being the supervisor: it owns the process tree, survives terminal close, and gives you a flat namespace of named sessions that you can attach to and detach from at will.
Why background sessions, not panes
A common starting point is "open tmux, split into panes, one Claude per pane." This works for two or three agents in the same project. It breaks the moment you want to:
Run agents in different working directories
Restart one agent without killing the others
Watch the output of an agent without committing your screen real-estate to it
Dispatch programmatically (a manager spawning a worker)
Detached sessions solve all of these. Each session gets a name and a working directory. You attach when you want to watch, detach when you don't, and kill when you're done. The agents themselves don't know whether anyone is attached.
The minimal config
Four commands cover 95% of the daily usage:
# Spawn a detached Claude Code session in a specific directory.
tmux new-session -d -s worker-spec-builder \
-c "$(pwd)" \
"claude --dangerously-skip-permissions"
# List running sessions.
tmux list-sessions
# Attach to a session (Ctrl-b d to detach again).
tmux attach -t worker-spec-builder
# Kill a session when the work is done.
tmux kill-session -t worker-spec-builder
That's the whole protocol. Everything else is convenience on top.
A wrapper helps once you've used it for a week. I dispatch with omni tmux deploy <name> --cwd <path> --prompt "<initial-prompt>" which is roughly:
tmux new-session -d -s "$name" -c "$cwd" "claude --dangerously-skip-permissions"
sleep 1
tmux send-keys -t "$name" "$prompt" Enter
The sleep 1 is unromantic but real: Claude Code needs a moment to register stdin before you can pipe a prompt at it. Less than a second and the prompt vanishes into the void.
Naming conventions that survive scale
Names are how you find sessions when you have eight of them running. The convention I've settled on:
<role>-<scope>
Where role is one of manager, worker, chad (a long-lived persona), or agent, and scope names the work. So:
manager-website, the workspace coordinator
worker-spec-builder, a one-shot worker
chad-omni, a long-running builder agent
agent-research-seo, a research one-shot
The role prefix matters more than it looks. tmux ls sorted alphabetically becomes self-organizing: all the managers cluster, all the workers cluster, you can see at a glance whether your fleet is balanced.
Avoid timestamp suffixes (worker-spec-1714329445) unless you need to spawn many copies of the same task. They're noise in the daily list and you'll never reattach to the right one without looking it up.
Attaching, capturing, killing
Three workflows you'll use constantly:
Attach to peek. tmux attach -t worker-spec-builder puts you in the session. Hit Ctrl-b d to detach without killing. The agent doesn't notice.
Capture without attaching. tmux capture-pane -t worker-spec-builder -p -S -100 prints the last 100 lines of the session to stdout. This is what you reach for when you want to know whether an agent has signaled done without context-switching to it. I script around this with omni tmux capture <name> --lines 60 for the common case.
Kill cleanly. tmux kill-session -t worker-spec-builder ends it. If the agent was mid-write, you'll lose that work; if it had committed, you keep it. This is why every agent I dispatch has the rule "commit and then signal done" baked into its spec, the kill is safe whenever there's a recent commit.
When this stops working
There are two failure modes worth naming:
Resource pressure. Every Claude Code session spawns its own MCP subprocesses. With six sessions plus a context-vault MCP each, you can hit 1.3 GB of memory on indexing alone. On a 16 GB Mac, that pushes you into swap fast. The fix is pressure-gated dispatch, not a fixed concurrency cap: probe vm_stat, sysctl vm.swapusage, and uptime before spawning, and back off if memory free drops below 25%.
Cached paths. Long-running agents and MCP subprocesses cache filesystem paths from their init. If you reorganize a workspace mid-session, every cached path becomes stale. Save calls return success while writes go nowhere. The fix is to either restart the affected sessions or refer to workspaces by identity (omni path <name>) instead of by hardcoded path. This isn't a tmux problem, but it shows up at the tmux layer because that's where the long-running processes live.
What this gets you
Once the convention is in place, two patterns become cheap:
The first is manager-worker dispatch: a long-lived manager session for a workspace, and short-lived worker sessions spawned to do specific tasks. The manager survives across days; workers come and go. Both live in the same flat tmux namespace, both follow the same naming convention, both can be attached to or captured from.
The second is fleet observation: tmux ls is your dashboard. Eight sessions running, eight rows of output, names that tell you who's doing what. No external tool. No web UI. The supervisor was already there.
The setup is unglamorous on purpose. tmux has been stable for two decades. Claude Code is two years old. Pinning the orchestration to the older, simpler thing means the orchestration keeps working even as the Claude Code surface area moves underneath.
That's the whole pattern. Detached sessions, named after their job, supervised by tmux, attached to when you want to watch and ignored when you don't. The first day you set this up it feels like overkill. By the second week, you wonder how anyone runs more than one agent without it.