At first I ran one Claude Code session per repository and fed it tasks in sequence. It worked until I wanted two things done at once. Two sessions in the same checkout step on each other's files, and one long session that does everything ends up with a context full of half-finished work. So I split the job in two roles: one session that plans and reviews, and short-lived sessions that write code, each in its own copy of the repo.
This post describes that setup as I use it today on my own repositories, including this site. Everything here comes from my scripts and my instructions file. Where something broke, I say so.
What does an orchestrator and worker setup for Claude Code look like?
The orchestrator is a Claude Code session opened at the repository root. It reads the code, talks to me, breaks work into tasks and reviews what comes back. It does not edit files in the repository. That rule is written in my global CLAUDE.md, the instructions file Claude Code loads in every project: if a change is needed, open a worktree; inside a worktree, read and edit only there.
A worker is a separate Claude Code session started in a separate git worktree, on its own branch, with a single task. When it finishes, it commits on that branch and writes a short report: what it did, which files it touched, what is still open and how to verify it. The report lives outside the repository so it never ends up in a commit.
Two open source tools hold this together. herdr is a terminal runtime for coding agents: a single Rust binary that runs inside your existing terminal, keeps sessions alive in a background server, labels each pane as working, blocked or idle, and exposes a CLI and socket API that agents can drive. treehouse manages a pool of reusable git worktrees per repository, so each agent or task gets an isolated checkout and dependencies and build caches survive between uses.
How is a single Claude Code session different from an orchestrator with workers?
The difference is mostly about where context and file changes live. This is how the two setups compare in my day-to-day use.
| Aspect | One Claude Code session | Orchestrator + workers |
|---|---|---|
| Parallel tasks | One at a time, or sessions colliding in one checkout | One worktree and one branch per task |
| Context | Grows with every task until it is noisy | Each worker starts clean with one task |
| Who edits code | The session you are talking to | Only workers; the orchestrator reads and reviews |
| Review | You review your own session's diff as it goes | The orchestrator reviews a finished branch plus a written report |
| Merging | Whatever branch you are on | One place: rebase on origin/main, run checks, push |
| Failure mode | Half-done edits mixed into the working tree | Leaked leases and stale worktrees if cleanup is skipped |
A single session is still the right call for a small fix in a repo you know well. I reach for workers when there are two or more independent tasks, or when one task is big enough that I want its exploration kept out of the planning session.
How does a new worker get created?
From the orchestrator, one command: a small shell script I wrote around herdr and treehouse, called herdr-worktree-new. It takes a branch name and the task text.
herdr-worktree-new feat/blog-table-block "Add a table block to the blog schema and renderer, with tests"The script does five things. It fetches from the remote. It asks treehouse for a worktree with a durable lease, which reserves it until someone explicitly returns it. It checks out the branch there: an existing local branch, a remote one tracked locally, or a new one. It opens that worktree as a new herdr workspace with a single tab running Claude Code. Then it waits until the agent's interface reports it is ready to accept input, and sends the task with a fixed suffix appended.
path=$(treehouse get --lease --lease-holder claude --no-fetch)
cd "$path" && git checkout -q -b "$branch"
herdr worktree open --workspace "$ws" --path "$path" --label "$branch" --no-focusThe suffix tells the worker, in plain words, to commit on its branch when done and write a summary to a report file, and not to create report files inside the repository. A stop hook notifies the orchestrator pane when the worker finishes or when it is waiting for an answer, so I see a line like [worker done] or [worker waiting] in the parent session without polling.
How does the orchestrator review and merge a worker's branch?
When a worker reports done, the orchestrator reads the report, then the diff on the branch. It checks the change against the repository's own rules. On this site those rules are in CLAUDE.md and a CODING_STANDARDS file: tests first, the system map and docs updated in the same diff, new UI strings added to both the English and Italian dictionaries, pnpm only.
If the work is good, delivery follows the repository's ship config. There are no pull requests on this repo; the branch goes straight to main after the gate passes.
git fetch origin && git rebase origin/main
pnpm lint && pnpm typecheck && pnpm test
git push origin HEAD:mainPushing HEAD to main works from inside a worktree even though main is checked out somewhere else. If the rebase moved anything, the checks run again. A merge conflict or a rejected push stops everything and comes back to me; force-pushing main is off the table. Typecheck is in the gate on purpose, because the production build for this site ignores type errors.
One small rule makes parallel branches far less painful: prose docs are written one sentence per line. When three workers each touch the same doc, a rebase usually merges their lines without a conflict.
How do you close a worker without leaking worktrees?
The second script, herdr-worktree-close, takes the herdr workspace id. It refuses to run if the worktree has uncommitted changes and prints the status instead. Otherwise it calls treehouse return on the path, closes the herdr workspace and deletes the report.
herdr-worktree-close w12
# dirty worktree -> exit 2, prints git status
# clean -> treehouse return <path>, workspace closedtreehouse return releases the lease, kills leftover processes in the worktree, resets it and puts it back in the pool. If it cannot confirm that no foreign processes remain, it leaves the worktree and the lease in place, which is the safe default.
What went wrong with this setup?
The worst one was leaked leases. A treehouse lease persists even when no process is running in the worktree; that is what makes it durable. When I closed a herdr workspace by hand instead of through the close script, the lease stayed. treehouse's default pool size is 16 worktrees, and one day every new worker failed because all 16 were marked in use while almost none had an agent running. The fix was releasing them with treehouse return, and the habit is to always close workers through the script.
Agent names collided. herdr agent names are unique across the whole herdr instance, not per pane, so naming every worker claude broke the second one. Now the name includes the workspace id, lowercased, because herdr requires lowercase names and workspace ids switch to uppercase letters after w19.
Tasks were sent too early. Sending the prompt right after starting the agent raced the app's own startup, and the text sometimes landed in the input box without being submitted. The worker sat there doing nothing. The script now polls herdr until the agent is interactive, with a cap, and only then sends the task.
Built-in worktree helpers left orphans. My instructions forbid Claude Code's own worktree tools for this flow, because they left orphaned worktrees in the repository's .claude/worktrees folder that nothing cleaned up. One pool with explicit leases is easier to audit.
What are the common mistakes when running parallel Claude Code agents?
Letting the orchestrator edit code. As soon as the planning session starts fixing things itself, its working tree diverges from what the workers see, and reviews turn into merges of two half-finished changes. Keep it read-only.
Giving a worker two tasks. A worker with one task produces a branch you can review in one pass. Two unrelated tasks in one branch mean a diff you either accept whole or untangle by hand.
Skipping the written report. The diff shows what changed, and the report says what the worker did not finish and how to check it. Without it, the orchestrator has to guess which parts of the branch are intentional.
Merging from the worker. If each worker pushes on its own, nobody runs the full gate on the rebased result. One place merges, after the checks.
Closing spaces by hand. With durable leases, closing the terminal does not free the worktree. Use the close script, or run treehouse return yourself.
Want your team working with agents in parallel?
I help engineering teams set up agentic workflows like this one on their own repositories: isolation, review gates and the rules agents follow.
Let's talkIf you want agents to do more than write code, for example handling tickets or documents, see how I approach AI agent development.



