Files
hermes-relay/docs/worktree-workflow.md

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

  1. One worktree = one branch = one feature/fix, in its own folder.
  2. Branch from current origin/dev, PR back to dev. CI green → merge --no-ff.
  3. main only receives dev→main release merges. Tag from main.
  4. 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 check git status before 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 dev in two worktrees errors — that's intentional. Keep local dev/main as clean mirrors in the primary checkout and do all task work on worktree branches.
  • Worktrees share the same .git, so a git fetch/git gc in any worktree affects all of them. Refs and stashes are shared; the working tree and per-worktree HEAD are 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.