One of my worker sessions took six tasks, confirmed receipt of two follow-up messages, and did nothing at all. No commits, no branch, not one file touched. From my orchestrator session it looked exactly like a worker that was quietly getting on with it.
That is the part of parallel Claude Code nobody warns you about. Starting workers is three commands, and I covered the mechanics in the parallel sessions post: launching with claude --bg, reading state instead of status, messaging between sessions. This post is the layer above it, from a night of running one orchestrator session and multiple workers on a live merge: what breaks, what it costs, and the questions the docs don't answer.
What does a Claude Code orchestrator actually do?
Less than I expected, and that took two corrections to learn.
The pull toward doing the work yourself is strongest when the orchestrator is competent: the next command is always faster to run than to delegate. But an orchestrator that analyses test files stops watching the streams, and the streams are the only thing it can see that nobody else can. The division that survived the night:
- The orchestrator owns the decision, the merge, the verdict record, and the report to the human.
- The workers own the code.
Reading two knowledge notes to decide whether a merge goes ahead is orchestration. Analysing a test file is a worker's job, even when the orchestrator would finish it faster.
There is a second half to the role that only shows up at 2+ workers: the orchestrator is the one channel the human reads. A report sitting in a worker's window has not been delivered. If I have to tell you "the answer is in tab 2", I have handed the synthesis work back to you, and with three workers that is three surfaces you now track. Pull the content, deliver the content.
How do I know which session needs me right now?
Poll claude agents --json --all and read the state field. I have observed five values, and one of them is a trap.
working, done, failed, and stopped mean what they say. blocked means the worker is waiting on a human; the waitingFor field names the reason, a permission prompt in every case I have hit. It waits indefinitely as far as anyone has measured. The row looks calm from the outside, status reads waiting, nothing errors, and a loop that only watches for done or failed never returns.
I provoked it deliberately while writing this: a worker launched in plan mode with a write task - a combination that has to block - went state: blocked, waitingFor: "permission prompt" by my first poll and has sat there for over an hour since. That measures the trap's shape, not how often ordinary workers fall in. There is no scriptable way to answer the prompt: you answer it from a terminal (claude attach <id>, or agent view), or you stop the session and take the loss - the transcript survives for a resume, the run does not.
This is the one-shot triage I actually run - paste it into any terminal, no setup:
python3 - <<'EOF'
import json, subprocess
rows = json.loads(subprocess.run(
["claude", "agents", "--json", "--all"],
capture_output=True, text=True).stdout)
for r in rows:
state = r.get("state")
name = r.get("name") or (r.get("sessionId") or "?")[:8]
if state == "blocked":
print(f"NEEDS YOU {name} ({r.get('waitingFor')})")
elif state == "failed":
print(f"FAILED {name}")
elif state == "working":
print(f"working {name}")
EOF
Anything it prints in the first two categories is where your attention goes. The prevention still beats the cure: put the permissions a task needs in reach before launching. A worker that never hits a prompt never blocks. And read what a row is missing, not only what it says: mid-run I found a session row that had lost the liveness fields every other row carried. A message to it failed as unreachable - it had ended between the listing and the send. One observed case, not a law, but since then I re-list before every send.
How do I know a worker actually did the work?
Not from anything the worker tells you. The silent worker from the intro is the proof: it accepted six tasks in its launch prompt, then took two follow-up messages, both confirmed delivered. The audit found HEAD unmoved, every task unstarted by direct code inspection, no worktree, no commits. Delivery receipts confirm the message arrived. They say nothing about whether anything happened next.
A silent worker and a working worker are indistinguishable from the orchestrator's seat until you go and look. "state: done" means a turn ended. The completion evidence is the file, the commit, the diff - something that exists outside the worker's own report. This is the same discipline as treating completion claims as unproven until you see the outcome, applied one level up.
The follow-up mistake I nearly made: on finding the dead worker, the reflex is to spawn a replacement. The better move was promoting a different agent that had just read all four relevant files for another question, and lifting its read-only restriction. Context is the expensive part of a fresh agent, and one that already has it is worth more than a clean start.
Why do my sessions still interfere when each has its own worktree?
Because a worktree isolates your checkout, and the checkout is not the only shared state.
Git worktrees are the settled answer to "my sessions overwrite each other's edits", and Claude Code creates them automatically before a background session writes. What they do not isolate: anything your hooks, caches, and config write to absolute paths. Two findings from the same night:
A hook nobody mentioned was rewriting three files on a five-minute cycle. A Stop hook in my global settings regenerates a capability index, throttled to once per 300 seconds. Three files a pull request was committing kept drifting, and the trap has teeth: the orchestrator's own turn ending fires the hook, so inspecting the drift re-creates it. Any commit touching such files has to land inside a fresh throttle window, with git status re-read immediately before the push.
Three of five sessions sat in the same tree on the same branch, because resuming an old session drops you back into whatever directory it lived in - right past the worktree launcher I built for exactly this. All three behaved well. That is the problem - the isolation came from self-restraint, and self-restraint is not a property of the system. Two writers in one tree is survivable. Two writers who do not know about each other is not.
One more environment gotcha that hits everyone who adopts worktrees: gitignored files do not travel. In my repo that meant no .env and no project MCP servers, because both are gitignored there - tracked files, including a committed .mcp.json, arrive like any checkout, and since v2.1.211 permission approvals are shared from the main checkout. The documented fix is a .worktreeinclude file, which copies matching gitignored files into every managed worktree; mine is a launcher that symlinks .env from the main checkout and passes the MCP config by absolute path. Before I built that, every worker in this repo booted with zero project MCP servers and nobody noticed for a week.
Who reviews what the workers produce?
The orchestrator, and this is where the night got humbling. Four defects surfaced, each made by a different actor, and each one was in a checker, not in the checked code:
| The defect | Who caught it |
|---|---|
A shell-quoting mistake made grep return 0 hits for a line that was verifiably there | a re-run with grep -F after the result contradicted a direct reading |
| A code reviewer reviewed the dirty working tree instead of the PR head | the orchestrator, noticing an identifier that did not match the committed one |
| A test harness stripped the executable bit off the file under test | CI, via a suite the author had scoped out locally |
| A malformed invocation of a review tool | the tool's own guard, refusing loudly with a reason |
One pattern, not four accidents: a verification instrument gets systematically less scrutiny than the thing it verifies. Nobody writes a test for their own grep. Three of the four failed in the reassuring direction - a clean-looking answer instead of an error. The fourth refused loudly with a reason, and was the cheapest of the four to catch. A tool that refuses loudly is worth more than one that is merely correct.
The consequence for multi-session work: "I verified it" is not a completion claim. The question is who checked, from where, and with what the first attempt did not use. My merge gate for worker output is external to the worker by construction, the same reasoning as the hook post: an instrument reporting on its own machinery is not independent evidence about the thing you care about.
Should my sessions coordinate without me?
They can, and mostly they should - with one boundary that took a shipped mistake to find.
The mechanics are solved: cross-session messaging delivers plain text between sessions - on the same machine up to about a million characters serialized, with queue caps of 50 accepted and 100 held messages. Peer corrections between my sessions caught real errors all night - a stale task, two wrong hypotheses, a retired rule I was about to hand to three workers.
Then a peer session overruled instructions I had written down, twice, and the worker complied both times. The peer's arguments were good - a corpus count of 180 occurrences against 2, a documented range. Neither was a hallucination. That is exactly what let it through: it did not feel like disobedience, it felt like being corrected by someone better informed.
If the peer is right, whose decision is being changed? A fact about the world - a version number, a measurement, a file's actual wording - verify it and adopt it. Something the human chose - style, scope, word count, what gets built - surface it and do not comply. Evidence changes facts. Only the human changes the human's rules.
The hard edge of the same boundary: a session that was denied a permission must never ask a peer to do the thing instead. That is permission laundering, and routing it back to the human is the only correct handling. Know what the boundary is made of, though. That a peer message can never approve a pending prompt is enforced; the never-ask half is an instruction to the model, not a guard - the same material as the "do not push" line one of my background sessions once ignored.
What does a multi-session day actually cost?
I summed the per-message usage fields in every session transcript on my machine for one 26-hour window: 63 session transcripts recorded activity, one of them the orchestrator, several of them workers.
The orchestrator session: 769 million total tokens across 1,771 assistant turns. The full split: 748.2M cache-read input (97.3 percent), 18.3M cache writes, 2.7M output, and a few thousand tokens of uncached input. The two biggest workers repeated the shape at 339M and 253M totals, with cache-read-to-output ratios of 289:1 and 420:1 against the orchestrator's 280:1.
Those are my numbers. Get yours - this reads only the local transcript files Claude Code already writes, one row per session:
python3 - <<'EOF'
import json, glob, os, time
WINDOW_H = 26
now = time.time()
rows = []
for path in glob.glob(os.path.expanduser("~/.claude/projects/*/*.jsonl")):
if now - os.path.getmtime(path) > WINDOW_H * 3600:
continue
turns = inp = out = reads = writes = 0
with open(path) as fh:
for line in fh:
try:
usage = (json.loads(line).get("message") or {}).get("usage")
except Exception:
continue
if not usage:
continue
turns += 1
inp += usage.get("input_tokens", 0)
out += usage.get("output_tokens", 0)
reads += usage.get("cache_read_input_tokens", 0)
writes += usage.get("cache_creation_input_tokens", 0)
if turns:
rows.append((inp + out + reads + writes,
os.path.basename(path)[:8], turns, reads, writes, out))
rows.sort(reverse=True)
print(f"{'total':>13} {'session':8} {'turns':>5} "
f"{'cache-read':>13} {'cache-write':>12} {'output':>9}")
for total, sid, turns, reads, writes, out in rows[:10]:
print(f"{total:>13,} {sid:8} {turns:>5} {reads:>13,} {writes:>12,} {out:>9,}")
EOF
That shape is most of the answer to "won't parallel sessions destroy my rate limits". In this window, adding workers multiplied re-ingested context far more than it multiplied output - each turn re-reads the conversation so far, until compaction trims it, and cached reads are billed at a fraction of fresh input. Despite the volume above, my weekly usage sat at 62 percent with a day to reset. One measured window on one plan, not a law. Session count is a real lever - every session draws the same quota - but the dominant term here was turn count times conversation length, cache-weighted. My long-running orchestrator out-consumed the two biggest workers combined, which is why handing the orchestrator seat to a fresh session with a tight handoff is a cost decision, not just a context one.
The honest total cost has a second line though: verification attention. Every stream you add is another stream whose claims you have to re-measure. In one night I inherited a handoff where two of three load-bearing claims were already false - "work is done" (the worker was still busy with follow-up work on a new branch) and "CI green" (it was failing). Not carelessness. State moves faster than prose. If you do not budget the time to re-check what workers and briefs tell you, parallelism just produces wrong conclusions faster.
This lives in primeline-ai/evolving-lite - the self-evolving Claude Code plugin. Free, MIT, no build step.
Honest scope
n=1 developer, one macOS machine, Claude Code 2.1.232-2.1.235, measured 2026-08-19 and 2026-08-20. The token numbers are sums of the per-message usage fields in each session's transcript, where a "turn" is one assistant message carrying a usage record; the 769M/97.3% figures are one machine over one 26-hour window, and the ratio is the point, not the absolute size. How transcript-token sums map onto the plan's usage meter is Anthropic's arithmetic, not mine - the 62 percent reading is an observation, not a derivation. The silent-worker case is a single occurrence - I cannot tell you how often delegation dies silently, only that it happened and that nothing surfaced it. The blocked probe was provoked deliberately (plan mode plus a write task), so it measures the trap's shape, not its frequency. The four-checker-defects table is one night's sample. Cross-session messaging measurements are macOS only; none of this is checked on Linux. And the blocked-worker question has a known open end: I have watched one wait indefinitely, and I have never watched one time out on its own. A probe for exactly that is running as I publish.

![Run Parallel Claude Code Sessions From One Terminal [2026]](/_next/image?url=%2Fblog%2Fparallel-claude-code-sessions-hero.webp&w=3840&q=75)

