Per-tree build roots · runner checkout boundaries · six-step runbook · decision matrix
Platform leads and mobile tech leads on small Mac Mesh pools juggling release, hotfix, and long-lived feature branches keep triaging three failure classes: CI and humans racing the same checkout, DerivedData and package caches shared across branches, and seat locks without a worktree path. This article answers who has which problem, then gives a conclusion: use auditable Git worktree layouts, per-tree build and cache roots, and lease fields that name the tree so parallelism becomes reviewable. You will get hidden taxes, a comparison table, a six-step runbook, hard parameters, and a decision matrix. Pair with shared build pool SSH and runners, seat locks and TTLs, affected builds and cache keys, golden image drift checklist, and public pages such as order plus help center.
Teams treat remote Macs as a mesh yet keep a single working copy per host. Release hotfixes and long-lived features then fight over the same checkout. The five patterns below show up constantly in 2026 tickets; writing them into your README and runner docs beats adding another machine you cannot reason about.
Racing git checkout against CI: an engineer switches branches while a self-hosted runner fetches another object set into the same path. Compilers read half-written module maps; tests miss fixtures; postmortems blame ghosts because nobody logged the tree path.
Shared DerivedData and package caches: parallel branches still point Xcode, SwiftPM, or CocoaPods caches at one physical root. One aggressive clean wipes another branch incremental state.
Seat locks without worktree identity: mutex records list hostname and PID but omit worktree path and short HEAD. Queue metrics cannot join to a concrete tree, so on-call SSHs and guesses.
Runner default paths overlapping interactive sessions: unattended xcodebuild shares the same login session as Archives and device prompts. GUI stalls and headless jobs amplify into nightly timeouts.
Cross-region fetch plus seat hold: mesh topologies that remote-compile without pinning cache generations let dependency retries balloon while a seat stays taken, echoing the merge-lane starvation described in Merge Queue and runner labels even when CPU looks idle.
Map the taxes to deliverables: a machine-readable worktree list, per-tree derived and dependency roots, a runner checkout allowlist, lock tuples (host, tree path, lease id, toolchain fingerprint), and one minimal clean versus incremental reproduction pair. Without those artifacts, do not promise parallel branching on shared pools.
Add an org lens: when nodes are communal infrastructure, reviews must answer whether the next engineer can still build on another registered tree after your change. Change tickets need affected paths, whether a global clean is implied, and rollback to single-tree mode.
Do not equate parallelism with sharing one mutable default directory. Even under disk pressure, evaluate git worktree sharing the object database before duplicating full clones to a second region. Otherwise you only move contention from Git to rsync and tarball caches. Pair with artifact fan-out and rsync when bytes leave the compile host.
Once taxes are named, teams ask whether to multi-clone, churn checkouts, or adopt worktrees. The next section compares disk, fetch cost, and operational risk on one page.
There is no universal answer, only a fit for branch parallelism, disk budget, and Git literacy. Print the matrix for the quarter; pick one default and document when you escalate.
| Mode | When it fits | Upside | Risk |
|---|---|---|---|
| Single directory, frequent checkout | Solo node, serial releases, no CI and human overlap | Lowest cognitive load; smallest disk | Racy with runners and humans; hard to audit |
| Multiple full clones | Low parallelism, ample disk, need hard isolation for hooks | Clear blast radius; strong compliance story | Higher fetch and drift maintenance |
| Git worktrees sharing the object store | Two to six active branches, medium disk, need auditable parallelism | Shared .git/objects; switching one tree does not destroy another working tree | Paths and prune policy must be explicit; novices can damage .git/worktrees metadata |
Parallelism needs every writable build artifact mapped to exactly one tree; otherwise red builds are attributed to luck.
If you choose worktrees, define a tree as a quadruple: bare or primary repo path, per-tree HEAD, derived data root, dependency cache root. If you cannot write it in three lines, you are not done.
Cheapest checks first; stop and save logs on failure. Align host aliases and runner labels with shared build pool SSH and runner orchestration.
Freeze bare repo location: pick one bare or primary working copy as object authority; forbid implicit git clone into unlisted paths from runner scripts.
Register every tree: export git worktree list --porcelain into an internal repo; fields must include path, branch, HEAD.
Bind derived and dependency roots: set per-tree OBJROOT, SYMROOT, DerivedData or SwiftPM cache directories; names must encode branch slug and short hash so clean scripts do not hit neighbors.
Isolate runner checkouts: dedicate a worktree or clone root for CI jobs; never share fuzzy defaults like ~/Projects/main with interactive sessions.
Embed leases in metadata: when taking a seat lock, write tree path, toolchain fingerprint, and expected duration; match fields in seat locks and TTLs to avoid dangling locks.
Exercise prune and reclaim: on staging, rehearse git worktree remove and orphan scans so you never delete directories still referenced by queued jobs.
~/mesh/repos/acme.git # bare recommended ~/mesh/wt/acme-release-2a9f # worktree: release/* ~/mesh/wt/acme-hotfix-7c1e # worktree: hotfix/* ~/mesh/ci/acme-merge # dedicated runner root DerivedData example: ~/mesh/dd/acme--release--2a9f ~/mesh/dd/acme--hotfix--7c1e
Note: if monorepo affected builds are already in place, fold the tree path into cache keys per affected builds guide so graph wins are not undone on disk.
Only facts you can name in paths and lock fields; avoid subjective Xcode feel. For image batches and snapshot rollback language, use golden image drift checklist.
xcodebuild clean versus per-tree incremental cleans; any global clean lists affected trees and job id ranges.Warning: do not roll a major Xcode bump, move DerivedData roots, and retag runners in one maintenance window; triangulation blocks bisect rollback.
Turn parallelism into checkboxes; if any box fails, fall back to serial releases or add capacity. Merge this table with queue policies in seat mutex guide.
| Scenario | Default | Hard prerequisites | Failure signal |
|---|---|---|---|
| Small team, low parallelism, no overlap | single directory plus calendar | dedicated node or explicit booking | mysterious reds during hotfix weeks |
| Small team, medium parallelism, overlap | two to four worktrees plus isolated runner root | per-tree derived roots; locks include tree path | clean scripts delete neighbor artifacts |
| Platform team, high parallelism, multi-region mesh | dedicated CI fanout plus human partition | label routing, seat caps, cache generation alignment | queue depth rises while CPU stays idle |
Relying on one engineer mental map centralizes risk when people rotate. Check in worktree lists and path tables so Mac Mesh becomes infrastructure, not folklore.
Common mistake: reaching for global clean on every red build; first verify shared DerivedData roots or half-finished jobs still holding seats.
Ad-hoc directories without tickets rarely survive audits asking which tree owned which disk at what time. When parallel branches need dedicated nodes, predictable regions, and contract-friendly SLAs, personal laptops and informal shared hosts fall short. For iOS CI, handoffs, and seat isolation on cloud Mac Mini capacity you can document, VpsMesh Mac Mini cloud rental is usually the better fit: scale pools by region and spec, and speak one ops language across paths, locks, and runners. See pricing, help center, and order when you need additional CI-only nodes.
Usually a single DerivedData root, a single CocoaPods or SwiftPM cache, and runner checkouts overlapping interactive sessions. Give each tree predictable subpaths with branch slugs and align fields with seat lock metadata.
Disk and fetch time via shared objects; you pay with stricter path hygiene and higher Git literacy. Review the decision table before rollout; pair with shared pool SSH guide for runner topology.
Merge Queue and runner labels address trunk merge starvation; this article addresses single-host worktree isolation and cache generations. Link both in the same change ticket.