5.8 KiB
Worktree Workflow
The day-to-day "how do I move fast without stepping on myself" guide. For the branching/release contract (when to merge, when to tag), see RELEASE.md "Branching policy" and
docs/decisions.md§23. This doc is only about worktrees — the parallel-folder layer that sits underneath that contract.
The one-paragraph mental model
A git worktree is a second working folder backed by the same .git. It's a
full checkout on its own branch that shares the object store and refs with your
main checkout — no clone, no re-download, instant. You alt-tab between folders
instead of running git checkout back and forth. That matters because switching
branches in one folder invalidates the Gradle build cache and forces the IDE to
re-index every time; separate worktrees keep each branch's caches warm in
parallel. This is the entire reason per-feature worktrees feel fast.
main ──●──────────────────●───────── released only; tags cut HERE
\ /
origin/dev ──●──●──●──●──●──●──────────── canonical integration ref
/ / /
feat/a ──● worktree A ┐
feat/b ────● worktree B ├─ one worktree = one branch = one unit of work
feat/c ──────● worktree C ┘
Four rules cover everything
- One worktree = one branch = one feature/fix, in its own folder.
- Branch from current
origin/dev, PR back todev. CI green → merge--no-ff. mainonly receivesdev→mainrelease merges. Tag frommain.- Worktrees are disposable — remove them once the PR merges.
The primary local dev checkout is a tracked-clean, fast-forward-only mirror of
origin/dev. It is not a staging area. Never commit, merge feature branches, or
queue release work there; update it with git merge --ff-only origin/dev after
fetching. This gives every session one integration authority even while several
worktrees are active.
When multiple reviewed branches must land as one batch, create
integration/<batch> from the latest origin/dev in a dedicated worktree, merge
the component branches there with --no-ff, and open one PR from that integration
branch to dev. One coordinator refreshes the batch against current dev, waits
for exact-head checks, and merges it. Other worktrees do not update local dev or
push directly to origin/dev.
In Orca (the normal path here)
This repo is developed inside Orca, which has its own worktree manager. Let Orca
create and tear down the per-feature worktree — that's how features ship in
parallel without index contention. Use the orca-cli skill / Orca worktree
commands rather than raw git worktree, so Orca tracks the worktree's state and
comment. Your job is just to keep each worktree to one unit of work and follow
the four rules above; gitflow is the merge discipline layered on top.
Why this matters for agents specifically: multiple Claude/agent sessions sharing a single checkout collide on the git index (the "shared worktree, concurrent sessions" failure mode — pathspec-commit workarounds, half-staged trees). One worktree per session removes the collision entirely. If you ever do run two sessions in one folder, commit with explicit pathspecs (
git commit -- <paths>) and checkgit statusbefore every commit.
Raw git worktree (fallback, outside Orca)
When you're not driving through Orca:
# Refresh the canonical integration ref without switching the primary checkout
git fetch origin dev
# Create a worktree for a new feature branch from exact origin/dev
git worktree add ../hermes-feat-bridge-scroll -b feature/bridge-scroll origin/dev
# ...work in that folder, commit, push, open a PR into dev...
# After the PR merges, remove the worktree and prune the branch
git worktree remove ../hermes-feat-bridge-scroll
git worktree prune
Useful checks:
git worktree list # every worktree + its branch + HEAD
git worktree remove <path> # delete a worktree (must be clean, or pass --force)
Gotchas
- A branch can be checked out in only one worktree at a time. Trying to check
out
devin two worktrees errors — that's intentional. Keep localdev/mainas clean mirrors in the primary checkout and do all task work on worktree branches. - Worktrees share the same
.git, so agit fetch/git gcin any worktree affects all of them. Refs and stashes are shared; the working tree and per-worktreeHEADare not. - Don't nest a worktree inside the repo — put it in a sibling dir
(
../hermes-feat-x), not under the repo root, or it gets swept into globs and IDE indexing. - Build outputs are per-folder. That's the point (warm caches), but it also
means three worktrees ≈ three
build/trees on disk. Gradle's local build cache is shared through the Gradle user home and can reuse compatible task outputs across worktrees. Prune merged worktrees so build trees don't accumulate. - Serialize heavy Gradle invocations on one host. Worktrees isolate source and output directories, but concurrent Android compiles, lint, tests, and APK packaging still compete for the same CPU, memory, daemon pool, and local cache. Keep one heavy Gradle lane active at a time; parallelism inside that invocation remains enabled.
How this maps to releasing
Worktrees change nothing about the release contract. Feature worktrees merge to
dev; releases are still cut by merging dev → main with --no-ff and tagging
from main (Android android-v*, Plugin server-v*, CLI+UI desktop-v*). Version bumps
happen on a dedicated release-prep branch that merges through a PR to dev — see
RELEASE.md.