My 8 Claude Code Sessions Could Merge Safely. None of Them Could Share Production.
A queue, a 6-branch merge train and a commit tripwire fixed git. Then I gave a single session the only key to production.

A single Claude Code session shipped 8 batches of work to production in 1 deploy. It hadn't written any of them.
The sessions that wrote them were no longer allowed to touch production (at my request, the orchestrator had taken that job away from them the same day). So is handing production to 1 session enough to let 8 sessions ship in parallel without getting in each other's way?
The merging part was already safe by then. It cost me a string of incidents a couple of months earlier, and it holds. Production is another animal, because a live database doesn't reconcile after the fact the way 2 branches do...
Let's see how I got there, what it fixed, and where it still breaks.
My Sessions Fought Over a Single Lock
I'm a solo dev. My product is a data SaaS with a daily ingestion job, running in production on my VPS with blue/green deploys, so a release never takes it down. I run 1 Claude Code session per task, and each session works in its own git worktree (a separate checkout of the same repo, so sessions never edit the same files on disk).
The number of sessions running at once varies. At one point, 1 session listed 7 active peers, which made 8 sessions in parallel. That's a snapshot, not a constant.
At first, every session could close its own branch by itself: merge, CI, deploy, the whole chain. On paper, that's autonomy.
Then one morning, 5 closures failed. Each session had launched its own closure in the background, and they were all fighting over the same single lock.
Worse, several sessions had started writing a shell loop that deleted the failure marker and retried. That marker is what keeps a failed closure visible until someone looks at it. Delete it, retry, and the failure never reaches anyone. And the workaround spread from agent to agent, like the outbreak in a zombie movie, except these zombies had shell access.
Every Incident Became a Script

Over a single week, each incident ended up as a mechanism, in this order:
- Red main. The main branch went red, merge after merge. Branches that were green on the fast CI, which only replays the tests a branch changed, contradicted each other once merged. Green alone and red together is the CI version of "works on my machine". The answer was a full test suite in an integration worktree before publishing main, so a collision now bounces back onto the guilty branch instead of breaking main.
- Killer test databases. The throwaway PostgreSQL test databases started killing each other. A guard meant to clean up orphan processes killed them by port, and test runs took each other's databases down: 756 connection failures in a single suite. The fix was total isolation per run, with a random data directory, a random port and no killing by port. As proof, 2 full suites of 3,410 tests each ran green in parallel.
- One lane. The fought-over lock became a single closure lane. A session pushes its branch and drops an entry in a queue, and 1 sequential launcher wakes up every minute to process it. The merge train was born the same day: up to 6 branches grouped together, with 1 integration worktree, 1 CI wait and 1 full suite. A conflict ejects that branch alone, and a red suite triggers a bisection that isolates the guilty branch and publishes the innocent ones. Later on, a train of 6 published 5 branches and ejected 1.
- The oversized branch. A branch of 50 files and about 5,200 lines got refused at final review. It was the same family of scope drift as the time my agents spent 4 days securing a 13-minute job, different incident. What came out of it is a pre-commit hook that blocks any branch past 15 files or 800 added lines. Crossing that line takes my explicit OK.
Soon after, the pattern became a rule: mechanize instead of documenting. Every instruction that says "always" gets turned into an executable check, whether that's a hook, a guard or a threshold, because an agent skims a paragraph and a hook doesn't.
This git layer is a known problem, and it shows up well outside my setup. A 2026 arXiv paper went through 33,596 agent pull requests across 2,807 GitHub repos. When the authors replayed 747 pairs of pull requests that were open at the same time, pairs from different agents hit a textual conflict 41.7% of the time, against 19.8% for pairs from the same agent. There's even a public repo, claude-code-merge-queue, that packages a comparable local queue for parallel Claude Code agents.
About 7 weeks later, that lane was taking 25 to 41 merges a day without breaking a sweat. The same week, the incidents kept coming, and none of them came from a merge.
41 Merges in a Day, and Git Held
Git counted 25 to 28 merges into main per day for 4 days in a row, then 41 the next day. The queue took all of them. The 4 problems of that week lived somewhere else.
The deploys came first. During a data maintenance pause, sessions each kept relaunching their own deploy. The canary check, which has to pass before a new release takes traffic, refused them, and they got in each other's way. A single session retrying is noise. Several retrying at once is a traffic jam on the only road into production.
Then came the heavy data jobs. Multi-hour backfills and recomputes were competing with the ingestion loop, and queries started timing out at the 30-second limit per query. It got to the point where ingestion had to be paused. For a data SaaS whose whole job is a daily ingestion, that's the product standing still.
Then came an ordering problem that only production could see. A branch needed its new column to exist in production before it merged, otherwise every recompute failed. Git has no idea what exists in the production database, so the right merge order was a production fact that no branch could see.
And then there was me, the human message bus. Every session asked me for its own go-ahead, which turned me into the bottleneck.
At the time of writing, the public tooling stops at the same line. claude-code-merge-queue is a local first-in, first-out queue on 1 machine, where a verification command has to pass before each merge. Claude Code Projects, in public beta, goes further: a coordinator conversation launches threads in parallel, threads whose pull request is approved or waiting in the merge queue show up as "Landing", and buttons offer to "Resolve conflicts", "Fix CI" or "Merge it", with a limit of 200 new threads per day. Its documentation page doesn't mention deployment. Both stop at the merge, and none of my 4 problems lived there.
What the 4 problems shared: merging fixed none of them, and each one needed a single session acting at a time.
Production Is a Single-Track Line

Production needs a single owner at any given moment, and better chat between sessions doesn't get you there.
Railways worked this out in the 19th century. On a single-track line, a driver can only take his train into a section if he holds that section's token, and there is 1 token per section. With Edward Tyer's electric instruments, patented in 1878, only 1 token could be out at a time. The safety comes from owning a unique object, not from trains talking to each other.
Code behaves like a double track. Branches run side by side and reconcile after the fact. Git does have a serialized point, the integration step, but the work itself stays parallel and gets reconciled there.
Production is the single-track section: the live database, the heavy jobs running against it, the deploy slot. There, the work itself doesn't parallelize, and 2 concurrent writes don't reconcile, they get in each other's way. Sessions that warn each other still each believe they're allowed to act. A single holder turns the rule into structure: if you don't hold the token, you don't enter the section.
Git can merge 2 branches, but nothing merges 2 deploys.
A Single Session Holds the Token
Until I set up an orchestrator, every session queued its own branch, relaunched its own deploy, started its own heavy jobs and asked me for its own go-ahead. So I asked the sessions to stop piling things up and to let orchestration carry what came next.
One evening, a dedicated orchestration session took over. It writes no code itself (every line goes to subagents), which is Nick Fury management: never in the fight, always picking the team. It drives the other sessions through Claude Code's cross-session messaging, which lets a session list the others and send them plain text (at the time of writing). My sidebar now sorts sessions into 3 groups, "to close", "in progress, do not close" and "next iteration". As I write this, they hold 23, 10 and 0.
That first evening, the orchestrator broadcast a version freeze to every session, in my words: "we finish what's in progress, we freeze a version, we iterate after." About an hour later, the freeze lifted for a single workstream, whose scope was announced once. About an hour after that, it called a general pause and wrote down the exact state: what was applied, where to resume, and what wasn't applied.
It also started asking the sessions instead of asking me. I'm only quoting the openings, translated, with names replaced by brackets:
Orchestration question, answer in a few lines: what exact command did you use to apply your ALTER TABLE…
Orchestration question, answer in a few lines: what exact command do you use to run your scripts in prod…
Resume (the auto mode check is responding again): run the…
[branch] was ejected from the train…
Production release: at [owner]'s request, the orchestration session now drives deployments…
Questions like the first 2 used to land on me.
The last message came 2 days after the freeze. At my request, the orchestrator told every session that it now drives deployments. The others kept pushing and queueing as usual, without relaunching any deploy or touching the queue. A single grouped deploy went out for 8 batches of work from other sessions. Then each session checked production in read-only mode and sent its verdict back.
This is a solo setup, with 1 machine and 1 production. I haven't tested any of it with a human team that deploys too.
The Handover Is the Real Memory
The orchestration sessions pass the baton through a handover file. This is its real structure, with generic content:
Orchestration handover, [date] [time]
Replaces: [previous handover, date, time]
## State at [time]
- [1 line per workstream]
## Delivered and verified (do not redo)
- [workstream]: [what shipped, how it was verified]
## In progress
- [workstream]: [session, current step]
## Remaining steps, in order
1. [step]
2. [step]
## Go-aheads already given
- [decision, in the owner's words]
## Backlog, no go-ahead yet
- [item]
## Rules learned today
- [rule]
A fresh session reads it and knows what not to redo, which decisions are already made and where to pick up.
The handover also carries the rules I gave, from 1 session to the next. I don't verify anything myself, by design. The session delegates the code, ships it, verifies it in production, and sends me short messages along the way, then a single final message. Only 1 heavy job runs at a time. A volume guard stops any write if the number of rows to change goes past the expected ceiling, and the pre-images (the rows as they were before) get saved first. So a fix that suddenly wants to touch far more rows than planned stops before writing anything. And "done" has a definition: a final dry run with no writes has to find 0 rows left to fix.
3 orchestration sessions took turns over 3 days. Each started with the same instruction: "resume the orchestration, read the handover in full first." Every new orchestrator wakes up with no memory and a note it left itself, which is basically the plot of Memento.
One morning's handover was stale by that evening. The evening one says at the top that it replaces it.
A handover that's dated, says what it replaces and lists the approvals already given stops a fresh session from asking me again for a go-ahead I already gave.
Copy This Before You Add Session 9
Each of these comes from something that broke first:
- Never let each session merge on its own. Keep a single write lane into main, and make it sequential, because 5 closures died in a single morning from sessions doing it themselves.
- Replay the full suite on the merged result before you publish, and bisect when it goes red. Branches that are green alone can still be red together.
- Turn every "always" in your instructions into an executable check, such as a hook, a guard or a threshold.
- Give production to a single session. Let it freeze the scope, run 1 heavy job at a time and group the deploys.
- Write dated handovers that say what they replace and which approvals are already given.
- Announce the scope once, at the start, then freeze it. It's the same idea as freezing the contract before the agent starts, applied to a whole fleet of sessions.
None of these rules needs a new tool, only a decision about who gets to write where.
The last decision of that week fit in 1 sentence, and the orchestrator couldn't write it for me.
Where Delegated Authority Hits a Wall
On its own, the orchestrator froze the scope, put the heavy jobs in order, grouped the deploys, restarted sessions and got a branch fixed.
What it couldn't do alone was lift a failure marker after a review refusal. That took an explicit sentence from me, because otherwise the classifier behind Claude Code's auto mode refused the action. That was the sentence.
Auto mode also refused remote shell commands in the middle of a chain, as calm as HAL 9000 saying "I'm sorry, Dave". That's the permission check doing its job. It still meant relaunching sessions once the check was answering again, which is where the "Resume" message came from.
The rest of the week had its own dents. The deploy step ships the most recent commit whose main CI is green, never an untested one, with a guard against rolling back. One day, it picked a 17-day-old commit, because it read a stale list of CI runs. The anti-rollback guard refused it, nothing was impacted, and the bug was fixed the next day. A repair branch also went through 5 review refusals in a row before it passed.
Claude Code's documentation on cross-session messaging draws the boundary, at the time of writing: a message from another session never counts as consent and can't approve anything, and permission limits stay with each session. So authority doesn't travel by message, by design.
I haven't measured the token cost of all this, or the human time it saves me, so I won't put a number on either.
That grouped deploy settles the question I started with. 8 batches of work from other sessions went out in 1 deploy, instead of 8 sessions each relaunching their own.
It also moved the weak spot onto the single holder. 3 orchestrators held the token in 3 days, kept on track by a handover file, and 1 auto mode refusal can stop the current one halfway through a chain.
On a single-track line, the token is a physical object, and only 1 can be out at a time. Mine is a session and a markdown file. Who picks it up when that session stalls mid-chain, I don't know yet.
Sources
- AI Agent Pull Requests on GitHub: Frequency, Structure, and Merge Conflict Rates, Xu, Subramanian and Karthik, arXiv, July 2026
- Let Claude coordinate ongoing work with Projects, Claude Code documentation, as of September 30, 2026
- Message your other Claude Code sessions, Claude Code documentation, as of September 30, 2026
- claude-code-merge-queue, a local merge queue for parallel Claude Code agents
- Single Line Working and Tablet Exchange Apparatus, Part 1, The LMS Society
This post may contain affiliate links. If you click them, I might earn a small commission — costs you nothing, and helps me keep shipping quality articles every day for your reading pleasure.
Running 8 Claude Code sessions in parallel without stepping on each other's toes needs clear gates. The production CLAUDE.md template in the kit spells out which session touches what, so merges stay safe and production stays live.