Pillar: git-worktree-mechanics | Date: May 2026
Scope: Git-native mechanisms for isolating parallel work: worktree lifecycle (create, prune, cleanup), branch naming conventions for agent identity, merge queue patterns (GitHub merge queue, Bors, Mergify, Aviator), automated rebase triggers on queue drain, worktree limitations (index locks, submodules, hooks), and CI integration with worktree-based pipelines.
Sources: 31 gathered, consolidated, synthesized.
Critical finding: Serial merge queues processing 100 PRs with 10-minute CI require 1,000 minutes of CI time — 100× more than batch approaches that preserve the same safety guarantee. At the same time, the productive ceiling for concurrent agents on a modern laptop is 5–7 before rate limits, disk consumption, and merge review overhead cancel out throughput gains; beyond 10, full orchestration infrastructure is mandatory.[2][16][31]
Git worktrees convert "invisible runtime corruption into visible merge-time conflicts" — the core isolation guarantee that makes multi-agent parallel development viable. Each linked worktree gets its own HEAD, index, and staging area, while sharing the object store with the main worktree. Creation takes seconds rather than the minutes required for a fresh clone, and a single fetch in any worktree synchronizes objects for all — parallel fetch with 4 workers reduces fetch time by approximately 71% compared to sequential execution.[9][28] Cursor IDE ships this pattern in production: its parallel agent feature creates one worktree per agent task, enabling simultaneous code edits without file-level collisions.[5]
Disk cost scales with file count, not history size. A 500 MB repository with five active worktrees requires at least 2.5 GB for checked-out files alone, with high-cost directories (node_modules, venv, target/) duplicating linearly per worktree.[10][28] Practical worktree ceilings are filesystem-dependent: community benchmarks place ext4 at 8–12 concurrent worktrees, XFS at 15–20+, and ZFS at 25+ with proper tuning — though Git itself enforces no hard maximum.[28] At the workflow level, 2–5 agents are manageable with tmux-based manual monitoring; 5–10 require task queuing with atomic task claims; 10+ demand conflict-detection pipelines and automated monitoring.[4][16][31]
Five shared resources account for the majority of worktree-related incidents in production agent pipelines: stash (git stash pop in one worktree applies stashes from all others, with no configuration override), hooks (all worktrees share .git/hooks/ — hardcoded paths break silently in linked worktrees), submodules (officially experimental and incomplete per Git documentation, with .git/modules/ state shared across all worktrees), concurrent git operations on the object store (Git is designed as a single-process tool; simultaneous writes risk corruption), and branch exclusivity (the same branch cannot be checked out in more than one worktree simultaneously).[8][9][16][24] Pre-flight checks for each should be part of every worktree bootstrap script. Hook path breakage is fixed with one line — always use git rev-parse --show-toplevel inside hook scripts rather than assuming the working directory.[1][14]
Worktree-based parallel CI reduces pipeline time by 63% for 3-branch scenarios: traditional sequential CI for 3 branches takes 24 minutes (8 min × 3); parallel worktree-based CI cuts that to approximately 9 minutes.[10] The single most common CI integration failure is missing the merge_group event type in GitHub Actions workflow triggers — status checks configured only for pull_request cannot satisfy merge queue requirements, causing indefinite queue stalls because the queue creates a new temporary branch and expects CI results for it, but the workflow never fires on merge group events.[15][26][29]
The merge queue ecosystem emerged from a "Not Rocket Science Rule" first implemented in the early 2000s — main always passes all tests. The tooling matured through Bors (2013, Graydon Hoare's Rust repo bot), Homu (2014), Bors-NG (2017, deprecated 2023), and enterprise internal solutions before GitHub's public beta in February 2023.[3][12] That native implementation reduced GitHub's own average merge wait times by ~33%. Enterprise adoption is substantial: Uber's SubmitQueue reduced CI wait times by 74% in their monorepo; Shopify's Shipit is used by over 90% of core application PRs.[3][18]
GitHub Native Merge Queue carries a fundamental architectural flaw: two-phase testing means the SHA that passes CI (a1b2c3d) is not the SHA that lands on main (7d8e9f0) — the server rebuilds the merge commit before landing. Tested code differs from merged code, creating an undetected regression window.[2] Both Aviator and Mergify guarantee "Tested SHA = Merged SHA." Beyond that gap, GitHub Native is FIFO-only (no priority reordering), lacks CI batching, has no analytics dashboard, and cannot be paused — making it fit only for small teams (<10 engineers), low PR volume (<20/day), and CI under 15 minutes.[2] Aviator's Parallel Mode extends further: an "eventual consistency" fallback merges all PRs through a completed one when another is stuck, so the queue never blocks on a single slow CI run — a property absent from both GitHub Native and Mergify and critical for heterogeneous CI check durations.[7][25] Aviator also supports "thousands of parallel queues" for monorepos via affected-target detection, validating independent CI check sets and merging concurrently when two PRs share no overlapping targets.[6][17]
Branch naming is not cosmetic — it is the routing mechanism for CI/CD workflows, issue tracker auto-linking, and multi-agent collision prevention. The agent/ prefix convention (e.g., agent/TASK-123-implement-auth) provides machine-parseable traceability back to the task system, prevents collision with human developer branches, and enables CI routing rules specific to agent work.[16][30] Branch names must stay under 60–80 characters — CI systems, Windows paths, and older git versions fail silently on longer names. Poor branch naming produces three concrete operational failures: CI pipelines that cannot parse patterns fail silently; unclear scopes generate merge conflicts from overlapping parallel work; and branches lacking ticket IDs require manual cross-referencing that compounds with fleet size.[30] IDE support for worktrees arrived in VS Code in July 2025 and JetBrains 2026.1 (March 2026) — prior to those versions, the CLI is the authoritative view of worktree state.[1][11]
Implications for practitioners: The worktree + merge queue combination forms a complete parallel agent pipeline only when all three layers are configured correctly: worktrees with proper lifecycle management (lock before agent runs, use git worktree remove not rm -rf, run git worktree prune before each CI run), task decomposition that maps file ownership before creating worktrees (overlapping file lists require sequential not parallel execution), and a merge queue matched to team scale — GitHub Native for <20 PRs/day with simple CI, Mergify for teams with expensive CI or monorepos, Aviator for high-throughput pipelines where the SHA-integrity gap and FIFO blocking become operational risks. Teams reaching the 5–7 agent ceiling should add task queuing with atomic claims before adding more agents, not after. Enabling git rerere (git config rerere.enabled true) at fleet startup pays compounding dividends: Git records and reapplies conflict resolutions automatically as the same hotspot files generate repeated merge conflicts across worktree branches.[1][8][16][31]
A Git worktree is "an additional working directory attached to the same repository."[9][14] Using git worktree add, a linked working tree is associated with an existing repository. Every repository begins with one main worktree (created by git init or git clone) and may have zero or more linked worktrees, each checked out to an independent branch.[8][9]
Each linked worktree maintains a two-pointer architecture that separates private worktree state from shared repository data:[9][11][28]
| Pointer | Points To | Contains |
|---|---|---|
$GIT_DIR |
.git/worktrees/<id>/ |
Per-worktree HEAD, index, per-worktree logs, lock marker |
$GIT_COMMON_DIR |
Main .git/ directory |
All shared data: objects, refs, config, hooks |
Each linked worktree contains a small .git file (not directory) pointing back to the main repo's object store.[9][11]
$GIT_DIR/worktrees/<id>/
├── HEAD # Per-worktree HEAD
├── index # Per-worktree index
├── config.worktree # Per-worktree config (if extensions.worktreeConfig enabled)
├── locked # Lock marker file (contains reason text)
├── gitdir # Points to worktree filesystem location
└── .git # Points back to main repository
| Resource | Scope | Notes |
|---|---|---|
| HEAD | Per-worktree | Independent branch checkout per worktree |
| Index (staging area) | Per-worktree | Completely independent staging per worktree |
MERGE_HEAD, REBASE_HEAD, CHERRY_PICK_HEAD | Per-worktree | In-progress operation state isolated |
refs/bisect/*, refs/worktree/*, refs/rewritten/* | Per-worktree | Pseudo-refs under $GIT_DIR |
Object store (.git/objects/) | Shared | Commits, trees, blobs never duplicated |
All refs (refs/heads/, refs/tags/, refs/remotes/) | Shared | Branch refs visible across all worktrees |
Hooks (.git/hooks/) | Shared ⚠️ | Path assumptions in hooks break in linked worktrees |
| Stash | Shared ⚠️ | git stash pop in one worktree applies stashes from others |
| Repository config | Shared (default) | Override with extensions.worktreeConfig |
.git/modules/ (submodules) | Shared ⚠️ | Switching submodule commits in one worktree affects all |
Without worktrees, concurrent agents encounter four structural failure modes:[1][11][28]
.git/index.lock causes fatal errors and potential system freezesKey finding: Worktrees convert "invisible runtime corruption into visible merge-time conflicts." Each agent operates on its own branch, in its own filesystem path, with no possibility of direct file-level collisions during active work.[1][11][28]
Cursor IDE ships this pattern in production — its parallel agent feature creates one worktree per agent task, enabling multiple simultaneous code edits without conflicts.[5]
Worktrees isolate agents at the filesystem level but provide no coordination at the semantic level. Two agents working on different features will frequently touch shared "hotspot files" — route definitions, configuration files, barrel exports, type definitions, and database schemas.[8] Worktrees also provide no runtime isolation — they share ports, databases, and process namespaces; layering lightweight containers on top of worktrees is required for full isolation.[1][11][28]
| Factor | Worktrees | Docker | Separate Clones |
|---|---|---|---|
| Creation time | Seconds | Seconds–minutes | Minutes |
| Storage efficiency | Shared objects | Image layers + delta | Full duplication |
| Filesystem isolation | Full | Full (OverlayFS) | Full |
| Runtime isolation | None ⚠️ | Full (namespaces) | None ⚠️ |
| Best for | Code-only workflows | Full-stack agents | One-off isolated testing |
git worktree add -b <new-branch> <path> <commit-ish>
Key flags for the add subcommand:[9][14][27]
| Flag | Effect | Git Version |
|---|---|---|
-b / -B <new-branch> | Create / reset branch at commit-ish | All |
-d / --detach | Create with detached HEAD | All |
--no-checkout | Skip automatic checkout (enables sparse-checkout config) | All |
--orphan | Create empty worktree with unborn branch | Git 2.44+ (2024) |
--lock | Keep worktree locked immediately after creation | All |
--guess-remote / --no-guess-remote | Match remote tracking branches | All |
-q / --quiet | Suppress feedback | All |
By default, add refuses to create a worktree when <commit-ish> is a branch already checked out by another worktree.[9][14][27]
| Pattern | Example Layout | Recommended By |
|---|---|---|
| Sibling directory | ~/projects/ml-pipeline-feature-auth/ (sibling of main repo) |
gitworktree.org[10] |
| Gitignored subdirectory | .trees/TASK-123/ inside main repo, gitignored |
Augment Code[11] |
| Named subdirectory variant | .worktrees/ or .claude/worktrees/ |
MindStudio[16] |
Most common naming convention: projectname-branchname for self-documenting layout.[10][19][11]
git worktree list # human-readable
git worktree list -v # verbose (locked/prunable annotations)
git worktree list --porcelain # machine-parseable (stable across versions)
The --porcelain format is stable across Git versions and is the correct choice for scripted parsing.[9][10] Each worktree is emitted as a blank-line-delimited stanza:
worktree /path/to/linked-worktree
HEAD abcd1234abcd1234abcd1234abcd1234abcd1234
branch refs/heads/master
worktree /path/to/locked-worktree
HEAD 5678abc5678abc5678abc5678abc5678abc5678c
branch refs/heads/locked-branch
locked reason why is locked
Locking prevents a worktree from being pruned, moved, or deleted:[9][10][19][27]
git worktree lock --reason "Agent running" .trees/TASK-123
git worktree unlock .trees/TASK-123
Critical use case: worktrees on portable devices, network drives, or hosting actively running agents. When locked, git worktree prune ignores the worktree even if its directory is missing.[9][27]
git worktree remove .trees/TASK-123 # safe removal (directory + metadata)
git worktree remove --force .trees/TASK-123 # force remove with uncommitted changes
# --force twice: remove locked worktrees
git worktree remove simultaneously deletes files from disk AND removes metadata from .git/worktrees/. It refuses to remove worktrees with untracked files or uncommitted changes unless --force is used. The main worktree cannot be removed.[9][10][27]
Key finding: Always prefergit worktree removeover manualrm -rf. Manual deletion leaves stale metadata behind, causing phantom entries ingit worktree listand "already checked out" errors.[1][4][10]
git worktree prune # remove stale administrative data
git worktree prune -n -v # dry run with verbose output
git worktree prune --expire <time> # only prune older than specified time
git worktree prune --verbose # report all removals
When a worktree directory is deleted with rm -rf, Git still considers it active, causing three specific problems:[10][19][27]
git worktree list shows phantom entriesgit branch -d refuses to delete branches it thinks are still checked outGit automatically runs a lightweight prune during git worktree add. Automatic garbage collection uses gc.worktreePruneExpire (default: 3 months). Force immediate prune: git config gc.worktreePruneExpire now.[10][19][27]
git worktree move <worktree> <new-path>
Limitations: cannot move the main worktree; cannot move linked worktrees containing submodules; requires --force twice if destination is locked.[9][14]
Worktrees rely on bi-directional pointers: the worktree has a .git file pointing to the main repo; the main repo has a path pointing to the worktree. If either is moved manually, pointers break:[10][19][27]
git worktree repair ../path/to/moved-worktree1 ../path/to/moved-worktree2
| Option | Default | Purpose |
|---|---|---|
worktree.guessRemote | false | Auto-track remote if unique match exists |
worktree.useRelativePaths | false | Relative internal links — repo can move without breaking worktrees (Git 2.46+) |
gc.worktreePruneExpire | 3 months | Auto-prune interval for stale worktree metadata |
extensions.worktreeConfig | false | Enables per-worktree config.worktree file |
git config extensions.worktreeConfig true
git config --worktree <key> <value>
# Stored in: $GIT_DIR/worktrees/<id>/config.worktree
Configuration variables that must NOT be shared across worktrees: core.worktree (never shared), core.bare (don't share if true), core.sparseCheckout (don't share unless consistent).[9][14][24]
Git enforces that the same branch cannot be checked out in more than one worktree simultaneously. This restriction prevents conflicting updates to the same branch ref — if two worktrees could modify the same branch independently, commits could be lost. Workarounds:[1][9][28]
-b <new-branch>--detach--force for a specific add operation — there is no config to globally disable the branch exclusivity constraint| Version | Release | Worktree Feature Added |
|---|---|---|
| Git 2.44 | 2024 | git worktree add --orphan — creates worktree with unborn branch (useful for gh-pages-style split deploy branches) |
| Git 2.46 | 2024 | worktree.useRelativePaths config + --relative-paths flag — enables extensions.relativeWorktrees so main repo can move without breaking worktrees |
Creating a worktree is faster than cloning because git worktree add shares the existing repository objects instead of re-fetching them. A single fetch in any worktree synchronizes objects for all worktrees, eliminating redundant network transfers.[28] Parallel fetch with 4 workers can reduce fetch time by approximately 71% compared to sequential execution.[28] (Note: this figure is sourced from gitcheatsheet.dev, a community reference resource, not a primary benchmark study; treat it as an order-of-magnitude guideline rather than a verified measurement.)
Working-directory files are still copied per worktree; disk cost scales with file count, not history size. A 500 MB repository with five active worktrees uses at least 2.5 GB for checked-out files alone.[10][28] High-cost directories (node_modules, venv, target/) are per-worktree and multiply linearly.[8][16]
| Filesystem | Approximate Community Benchmark | Notes |
|---|---|---|
| ext4 | ~8–12 | Typical developer machine ceiling |
| XFS | 15–20+ | Better inode scaling |
| ZFS | 25+ | Dynamic inode allocation with proper tuning |
Note: These figures are community-reported practical ceilings, not hard limits enforced by Git. Git itself imposes no maximum worktree count; actual limits depend on available disk space, memory, and inode allocation on the host filesystem. Source: gitcheatsheet.dev (raw_28.md) — no primary benchmark study or kernel/Git documentation backs these specific numbers.
| Agent Count | Practical Ceiling | Required Infrastructure |
|---|---|---|
| 2–5 | Standard workflow (tmux-based or manual) | Manual monitoring; no additional tooling required[4][31] |
| 5–7 | Modern laptop productive ceiling | Rate limits, disk consumption, merge review overhead cancel throughput gains[16] |
| 5–10 | Requires task queuing | Shared JSON with locking for atomic task claims, headless execution, dependency-aware scheduling[31] |
| 10+ | Full orchestration required | Task queues, rate limit handling, automated monitoring, conflict detection pipelines[16] |
Traditional sequential CI for 3 branches takes 24 minutes (8 min × 3); worktree-based parallel CI cuts that to approximately 9 minutes — a 63% reduction.[10] With 30-minute CI and a serial merge queue, maximum theoretical throughput is approximately 48 PRs/day; batching and parallelism multiply this significantly.[18]
Key finding: Parallel fetch with 4 workers cuts fetch time by ~71%; worktree-based parallel CI cuts pipeline time by ~63% for 3-branch scenarios. The shared object store is the mechanism behind both gains — no re-fetching, no re-storing.[28][10]
Pair git worktree add with sparse checkout to constrain disk usage for agent-relevant file domains in monorepos:[1][11]
git worktree add --no-checkout .trees/TASK-123 -b agent/TASK-123 origin/main
cd .trees/TASK-123
git sparse-checkout set <paths>
git checkout
See also: Monorepo Tooling (Bazel/Buck integration with sparse checkouts)
.index.lock) IssuesGit creates an index.lock file before any write operation to prevent concurrent modifications. If the agent process crashes during a git operation, the lock file persists. Each worktree has its own index lock at .git/worktrees/<n>/index.lock, so crashes affect only the specific worktree.[8][14]
Common causes of stale locks: background IDE/editor processes (VS Code, JetBrains keep background git processes running), file watchers, git hooks. Diagnosis:
find .git/worktrees -name "index.lock"
# Remove only if no Git process is running
Multiple checkout with submodules is still experimental and support is incomplete per official Git documentation.[8][9][14][24]
| Submodule Restriction | Impact |
|---|---|
Cannot use git worktree move on worktrees with submodules | Blocks filesystem reorganization |
Unclean worktrees with submodules require --force to remove | Complicates automated cleanup |
.git/modules/ is shared — switching submodule commits in one worktree affects all others | Cross-worktree state pollution |
| Official docs: "NOT recommended to make multiple checkouts of a superproject" | No upstream fix planned |
Mitigations: prefer git subtrees or vendoring over submodules; configure submodule.recurse true, diff.submodule log, status.submodulesummary 1.[8][9][24]
All worktrees share the same hooks from the main .git/hooks/. A hook that uses hardcoded paths or assumes it's running at the repo root breaks in linked worktrees.[1][8][14][24] The fix: always use git rev-parse --show-toplevel in hooks:
#!/bin/bash
ROOT=$(git rev-parse --show-toplevel)
npm --prefix "$ROOT" run lint-staged
Per-worktree hook customization requires core.hooksPath set per-worktree using extensions.worktreeConfig.[1][8][14][24]
git stash pop in one worktree can apply a stash created in another. This is a structural behavior, not a bug, and has no configuration override.[24]
IDE indexes run independently per worktree, creating duplicate CPU and disk usage with no known workaround.[24]
| IDE | Worktree Support Added |
|---|---|
| VS Code | July 2025 |
| JetBrains | 2026.1 (March 2026) |
Treat CLI as authoritative worktree state view for IDE versions prior to these dates.[1][11][20]
Git provides no mechanism warning when parallel agents modify identical files on different branches — conflicts only surface at merge time.[1][8][16] Mitigations: assign agents to strictly non-overlapping file domains; enable git rerere for automatic conflict resolution reapplication; pre-plan file ownership before launching agents. Enable rerere with git config rerere.enabled true — Git then records conflict resolutions and reapplies them automatically when the same conflict recurs across worktree merges.
Running concurrent git operations across multiple worktrees can cause corruption of the shared .git object store. Git is designed as a single-process tool. The safe model: each agent runs its own git operations in its own worktree; no two agents run git commands simultaneously in the same worktree.[16][28]
| Area | Behavior | Risk Level |
|---|---|---|
| Index | Per-worktree (independent staging) | Safe |
| HEAD | Per-worktree (independent) | Safe |
| Object store | Shared (efficient disk use) | Safe (read-only concurrent access) |
Refs (refs/heads/, etc.) | Shared across worktrees | Caution (branch name collisions) |
| Config | Shared (main .git/config) | Caution |
| Stash | Shared across all worktrees ⚠️ | High |
| Hooks | Shared from .git/hooks/ ⚠️ | High (path assumptions break) |
| Submodules | Shared .git/modules/; move blocked ⚠️ | High |
| Branch checkout | Same branch cannot be in two worktrees ⚠️ | Enforced by Git |
Key finding: The five high-risk shared resources — stash, hooks, submodule data, concurrent git operations on the object store, and branch exclusivity — account for the majority of worktree-related incidents in production agent pipelines. Pre-flight checks for each should be part of any worktree bootstrap script.[8][16][24][28]
| Prefix | Semantic Meaning | Machine-Routable |
|---|---|---|
feature/ | Developing new features | Yes — triggers test workflows |
bugfix/ | Fixing bugs | Yes |
hotfix/ | Critical production fixes | Yes |
release/ | Preparing a production release | Yes — triggers deploy workflows |
chore/, refactor/, docs/, test/, ci/ | Non-feature work | Partial |
agent/ | AI agent-driven branches (emerging convention) | Yes — enables agent-specific CI routing |
feature/user-login-redesign not feature/userloginredesign[30]..), names ending with .lock (reserved by git)[30]The agent/ prefix with task ID provides clear identification of machine-generated branches, traceability back to the task system, collision prevention with human developer branches, and enables CI/CD routing rules specific to agent work.[16][30]
| Pattern | Example | Use Case |
|---|---|---|
| Agent + task ID | agent/TASK-123-implement-auth | Single-agent task tracking[30] |
| Agent + feature name | agent/feat-auth-module | Feature-scoped work[16] |
| Worktree slug + agent name | worktree/auth-module--claude-01 | Multi-agent environments[16] |
| Spec-linked names | feature/spec-3.2-jwt-refresh | MindStudio playbook — maps branches to spec sections, prevents scope creep[16] |
Embedding the ticket ID in the branch name enables automatic linking in issue trackers. Specifically, it enables: JIRA, Linear, and GitHub to auto-link branches to tickets, auto-populate PR descriptions from ticket titles, show sprint boards with active branches, and generate release notes from merged branch names.[30]
feature/JIRA-456-add-dark-mode
bugfix/GH-123-fix-login-redirect
agent/TASK-123-implement-auth
Modern CI/CD pipelines use branch names for workflow routing:[30]
feature/** → run tests)staging/** → deploy to staging)fix/ and feature/ branches)Validation regex for CI enforcement:[30]
^(feature|bugfix|hotfix|release|chore|refactor|docs|test|ci|agent)\/[a-z0-9][a-z0-9-\/]*$
Key finding: Poor branch naming creates three concrete operational failures — CI/CD pipelines that cannot parse patterns fail silently; unclear scopes generate merge conflicts from parallel work on overlapping areas; and branches lacking ticket IDs require manual cross-referencing that compounds as agent fleet size grows.[30]
| Strategy | Best For | Trade-offs |
|---|---|---|
| Worktree-Per-Task (recommended default) | Ephemeral agents completing work in under an hour | Minimal overhead; agents reassigned as needed; clean lifecycle[1][11][20] |
| Worktree-Per-Agent | Persistent specialist agents in extended sessions | Warm dependency caches, accumulated context; harder lifecycle management[1][11][20] |
TASK_ID="${1:?Usage: $0 <task-id> [base-branch]}"
BASE_BRANCH="${2:-main}"
BRANCH_NAME="agent/$(sanitize_branch_name "${TASK_ID}")"
WORKTREE_PATH=".trees/${TASK_ID}"
git fetch origin main
git worktree add -b "${BRANCH_NAME}" "${WORKTREE_PATH}" "origin/${BASE_BRANCH}"
cd "${WORKTREE_PATH}" && npm ci --prefer-offline
git worktree lock --reason "Agent running" "${WORKTREE_PATH}"
To prevent runtime port collisions across worktrees, use branch name hashing to assign stable ports:[11][20]
PORT=$(( 3100 + $(echo "${BRANCH_NAME}" | cksum | cut -d' ' -f1) % 6899 ))
This assigns a stable port in the range 3100–9999 per branch, deterministically derived from the branch name.
| Strategy | Best For | Trade-offs |
|---|---|---|
Symlink node_modules | Byte-identical package-lock.json | Fast; risky if locks diverge between worktrees |
| Copy-on-Write | macOS APFS only | Zero upfront cost; not portable |
| npm Workspaces | Monorepos | Shared top-level node_modules |
Offline Cache (npm ci --prefer-offline) | Universal default | Longer first install; always safe |
Worktree isolation only works for genuinely independent tasks. "If two tasks have overlapping file lists, they need to be sequential, not parallel."[4][16][31] Map file ownership before creating worktrees. The most common mistake: "launching agents before planning file ownership" creates expensive merge conflicts requiring rework.[4][16][31]
Distribute an AGENTS.md project brief containing architecture decisions, coding conventions, API contracts between components, and active task ownership list. This prevents context drift while maintaining filesystem isolation. A "living spec" serves as the shared coordination artifact all agents read from and write to.[16][31]
A canonical use case for worktrees is applying an emergency fix to a stable branch without disturbing in-progress work. The full lifecycle in three commands:[9]
git worktree add -b emergency-fix ../temp master
cd ../temp
git commit -a -m 'emergency fix'
cd -
git worktree remove ../temp
Establish a clean test baseline before handing off to agents:[1][11]
npm lint && npm testStandard practice is rebasing feature branches on latest main before merging. For fleets beyond 5–7 agents, FIFO merge queues with tiered conflict resolution automation handle ordering and reduce manual review overhead.[1][11]
A structured multi-agent coordination pattern with three layers:[1][11][20]
All agents share a Context Engine for semantic codebase understanding.[1][11][20]
| # | Failure Mode | Root Cause | Mitigation |
|---|---|---|---|
| 1 | File ownership conflicts | Launching agents before planning file assignments | Pre-map file ownership; enforce non-overlapping domains[31] |
| 2 | Node modules corruption | Shared symlinked dependencies with concurrent installs | Keep modules isolated per worktree[31] |
| 3 | Context drift | Architectural decisions made mid-task not propagated | Update shared brief immediately on any decision[31] |
| 4 | Undefined completion | Agents lack explicit exit criteria | Define acceptance criteria per task before agent launch[31] |
| 5 | Skipped code review | Speed of generation → skipping review step | AI-generated code still requires human review regardless of speed[31] |
echo '.trees/' >> .gitignore
git worktree add .trees/TASK-123 -b agent/TASK-123 origin/main
cd .trees/TASK-123 && npm ci --prefer-offline
git worktree lock --reason "Agent running" .trees/TASK-123
git worktree list --porcelain
# ... agent runs ...
git worktree unlock .trees/TASK-123
git worktree remove .trees/TASK-123
git worktree prune
Key finding: The productive ceiling for parallel agents on a modern laptop is 5–7 concurrent before rate limits, disk consumption, and merge review overhead cancel out throughput gains. Beyond 10 agents, full orchestration infrastructure is mandatory.[16][31]
# Prune before creating (avoids stale metadata errors)
git worktree prune
git worktree add ../acme-build release/v2.3
# ... run build and tests ...
git worktree remove ../acme-build
A common CI failure is when a previous worktree wasn't cleaned up: error "worktree path already exists." Solution: add force cleanup to CI script:[10][27]
git worktree prune
git worktree remove --force ../ci-build-* || true
git worktree add ../ci-build-$CI_COMMIT_SHA $CI_COMMIT_SHA
git worktree add --detach ../review-$PR_NUMBER
git fetch origin pull/$PR_NUMBER/head:pr-$PR_NUMBER
git -C ../review-$PR_NUMBER checkout pr-$PR_NUMBER
cd ../review-$PR_NUMBER && npm ci && npm test
#!/bin/zsh
git fetch --all --prune
WORKTREES=$(git worktree list | awk '{print $1}' | tail -n +2)
for DIR in $WORKTREES; do
cd "$DIR" || continue
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "detached")
if [[ "$BRANCH" != "detached" ]]; then
git rebase origin/"$BRANCH"
fi
cd - > /dev/null
done
merge_group Event TriggerWhen using GitHub Merge Queue, CI must be updated to include the merge_group event trigger:[15][26]
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
merge_group:
types: [checks_requested]
For third-party CI providers, configure to run on branches matching pattern gh-readonly-queue/{base_branch}/*.[15][26]
Known pitfall: Having required status checks configured only for pull_request triggers causes the merge queue to stall — the queue creates a new temporary branch and expects CI results for it, but the workflow only fires on PR events, not merge group events.[15][26][29]
wt CLI ToolTools like wt support wt status --ci, which displays CI/CD pipeline status per worktree — showing ✓ (pass), ✗ (fail), or ● (pending) for each branch's latest pipeline run. This feature requires gh (GitHub CLI) or glab (GitLab CLI) to be installed and authenticated.[10]
| Practice | Rationale |
|---|---|
Name worktrees with descriptive prefixes (project-review-, project-build-) | Enables scripted pattern-match cleanup[10][19][27] |
| Automate cleanup with scheduled cron jobs or pre-push hooks | Prevents stale metadata accumulation[10] |
Use --lock for portable/network worktrees | Prevents accidental prune of active worktrees[10] |
Add .trees/ or .worktrees/ to .gitignore | Prevents accidental tracking of worktree directories[11][16] |
Use --porcelain format for all scripted output parsing | Stable format across Git versions[9][10] |
Run git worktree prune before git worktree add in CI | Avoids "path already exists" CI failures[10][27] |
alias gwtclean='git worktree prune --verbose && git worktree list'
git config --global alias.wt-clean '!git worktree prune -v && git worktree list'
Key finding: The single most common CI/CD integration failure is missing themerge_groupevent type in GitHub Actions workflow triggers. Status checks configured only forpull_requestcannot satisfy merge queue requirements, causing indefinite queue stalls.[15][26][29]
Merge skew occurs when changes appear compatible individually but break when merged together into an updated codebase — two PRs may each pass CI independently, but break main after merging.[29][18]
Semantic conflict: a change does not integrate properly even though Git reports no textual conflict. Examples:[29]
"CI lacks awareness of the bigger picture" — it cannot pre-emptively perform checks on hypothetical future states resulting from multiple PR merges.[29][18]
The fundamental guarantee: "main branch always passes all tests."[3][29]
| Year | Tool / Event | Key Contribution |
|---|---|---|
| Early 2000s | Ben Elliston's cron + DB system | First implementation of "Not Rocket Science Rule" — always passing tests[3][12][22] |
| 2013 | Bors (Graydon Hoare) | Bot for Rust repo — merged to temp branch, ran tests, only fast-forwarded main on pass[3][12] |
| 2014 | Homu (Barosl Lee) | Tested changes before landing by temporarily combining with up-to-date main; launched homu.io 2015; stagnated ~2018[3][22] |
| 2017 | Bors-NG (Michael Howell) | Open-source replacement; faster, easier self-hosting; deprecated 2023 when GitHub launched native queue[3][12] |
| 2017 | Bulldozer (Palantir) | Automated merges when checks passed; auto-updated branches[3][12] |
| 2018 | Mergify (Julien Danjou & Mehdi Abaakouk) | SaaS "merge queue as a service" — flexible rules, priority configs, batch merging[3][22] |
| 2019 | Kodiak (Christopher Blump) | FIFO PR processing, proper queue management eliminating race conditions; gained adoption after Vercel CEO endorsement[22] |
| 2019 | GitLab Merge Trains (12.0) | Built-in queue — sequences MRs, runs pipelines on each result before landing[3][22] |
| 2023 Feb | GitHub Merge Queue (public beta) | Native GitHub implementation; reduced GitHub's own average wait times by ~33%[3][12][22] |
| 2023 Jul | GitHub Merge Queue (GA) | Generally available; Bors-NG formally deprecated[3][12] |
| Company | Tool | Reported Impact |
|---|---|---|
| Uber | SubmitQueue | Reduced CI wait times by 74% in their monorepo[3][18] |
| Shopify | Shipit | Over 90% of core application PRs use the merge queue[3][18] |
| Strava | Butler | Enforces orderly merging for fast-moving teams[3] |
GitHub engineers were merging approximately 1,000 PRs monthly by 2016. Internal systems matured 2020–2021 before public beta in February 2023. The implementation reduced average wait times by ~33% internally.[3][12][22]
merge_group webhook dispatched, awaits CI resultsWhen the queue drains (a PR at the front is merged), merge queue tools automatically:[6][17][18]
This automated rebase trigger fires on every queue drain event and is the mechanism that maintains the "main always passes" guarantee across concurrent PRs.
Batching multiple PRs into a single CI run can reduce CI time by up to 66% when most PRs don't have semantic conflicts.[18][29] On batch failure, the queue bisects to find the culprit. Concrete throughput comparison:[2]
| Scenario | CI Duration | PRs | Total CI Time |
|---|---|---|---|
| Bors/batch merge | 10 min | 100 | 10 minutes total |
| GitHub Merge Queue (serial) | 10 min | 100 | 1,000 minutes total |
Key finding: Serial merge queues with 10-minute CI and 100 PRs require 1,000 minutes of CI time — 100× more than batch approaches. Batching with bisect-on-failure preserves the safety guarantee while eliminating the serial throughput bottleneck.[2][18]See also: Post-Merge Agent Recovery (rebase vs. restart decision logic after queue drain)
Built-in; no additional setup beyond branch protection rules. Public beta February 2023, GA July 2023. Reduced GitHub's own average wait times by ~33%.[3][12]
Technical limitations:[2]
Fit: Small teams (<10 engineers), simple CI (<15 min), low PR volume (<20/day), no monorepo complexity.[2]
SaaS, launched 2018. Addresses key GitHub Native gaps — CI batching, two-step CI validation, queue pause, analytics, Slack/Datadog integrations.[2][13][21]
Capability gaps vs. Aviator:[13][21]
Fit: Teams with 20+ daily contributors, expensive CI, monorepos, complex validation requirements.[2]
Most feature-complete of the three. Supports both cloud and self-hosted (GitHub Enterprise) deployment. Open-source CLI (av) for stacked PR workflows.[6][13][23][25]
| Feature | Aviator | Mergify | GitHub Native |
|---|---|---|---|
| Speculative checks | ✅ | ✅ | ✅ |
| CI batching | ✅ | ✅ | ❌ |
| Parallel mode testing | ✅ | ❌ | ❌ |
| Two-step CI validation | ✅ | ✅ | Limited ⚠️ |
| Monorepo batching | ✅ | ✅ | ❌ |
| Affected targets (multi-queue) | ✅ | ❌ | ❌ |
| Multiple priority levels | ✅ | ✅ | ❌ (jump to front only) |
| Queue pause | ✅ | ✅ | ❌ |
| Auto-retry on flaky tests | ✅ | ✅ | ❌ |
| Analytics dashboard | ✅ | ✅ | ❌ |
| Tested SHA = Merged SHA | ✅ | ✅ | ❌ ⚠️ |
| Stacked PR support | ✅ | ❌ | ❌ |
| Fast forwarding (linear history) | ✅ | ❌ | ❌ |
| Cross-repo PR dependencies | ✅ | ❌ | ❌ |
| Self-hosted option | ✅ | Limited | N/A |
| GraphQL API | ✅ | ❌ | ❌ |
Aviator's "affected targets" strategy focuses testing only on parts of the codebase directly impacted by a pull request. Aviator can create "thousands of parallel queues" for monorepos, validating independent CI check sets and merging concurrently. When two PRs have no overlapping affected targets, they can be tested and merged independently in any order.[6][17][18][25]
Beyond merge queues, dedicated research tools exist for semantic conflict detection:[18][29]
Key finding: GitHub Native Merge Queue's two-phase testing flaw — where the tested SHA differs from the merged SHA — is a fundamental architectural gap that Aviator and Mergify both solve with "Tested SHA = Merged SHA" guarantees. For any team where post-merge regressions have operational consequences, GitHub Native is insufficient.[2]See also: Monorepo Tooling (monorepo-specific queue strategies with Bazel/Buck affected target detection)
Parallel Mode creates Draft PRs combining queued changes and runs CI in parallel optimistically. On failure, it closes subsequent Draft PRs and restarts the queue.[7][25]
| Scenario | Behavior |
|---|---|
| First Draft PR fails; no other PR queued | Original PR is dequeued[7][25] |
| First Draft PR fails; others are queued | Bot closes all subsequent Draft PRs; restarts queue without the failing PR[7][25] |
| A Draft PR's CI is stuck but a subsequent one completes | "Eventual consistency" logic merges all PRs through the completed one[7][25] |
| Flaky test on a Draft PR | Configurable retry count; TestDeck integration for flake detection[7][25] |
The max_parallel_builds config controls concurrent Draft PR builds.[7][25]
Aviator's open-source CLI (av) supports stacked PR workflows. av pr --queue queues entire stacks; the tool handles stack-aware ordering and syncing. No equivalent capability exists in Mergify or GitHub Native Merge Queue.[7][13][25]
| Method | History Shape | Notes |
|---|---|---|
| Merge commit | Non-linear, full history | Traditional; default for most tools[15][17] |
| Rebase | Linear, commits preserved | Adds commits to head of target branch[15][17] |
| Squash | Linear, commits condensed | Single new commit per PR[15][17] |
| Fast forward (Aviator only) | Linear, no merge commits | Maintains cleaner history; not available in GitHub Native or Mergify[6][13] |
Key finding: Parallel Mode's "eventual consistency" fallback — merging all PRs through a completed one when another is stuck — means Aviator's queue never blocks on a single slow CI run. This property is absent from both GitHub Native and Mergify, making Aviator the only production-grade option for high-throughput pipelines with heterogeneous CI check durations.[7][25]See also: Post-Merge Agent Recovery (rebase vs. restart decision logic for agents after merge queue drain events)