Skip to content

SPIKE-27.10 — cmux worktree primitives vs native Claude Code worktree features

SPIKE-27.10 — cmux worktree primitives vs native Claude Code worktree features

Section titled “SPIKE-27.10 — cmux worktree primitives vs native Claude Code worktree features”

Type: Research spike (investigation only — no code changes). Subtask: ID-27.10. Author: cmux worker subo-id-27. Date: 2026-05-29.

Audit cmux’s hand-rolled worktree management (.claude/skills/session-driver-cmux/scripts/launch-worker.sh) against the native Claude Code (CC) worktree features it now duplicates, and rule per-primitive whether cmux can ADOPT the native feature, must KEEP-CMUX (keep the custom path), or it is NO-GO.

Hard constraint (Liam, S282) — non-negotiable

Section titled “Hard constraint (Liam, S282) — non-negotiable”

Any convergence MUST preserve cmux’s durable, attachable terminals + mid-session OQ-escalation: the operator must keep the ability to cmux attach --workspace <ref> into a worker’s terminal and converse with the sub-orchestrator mid-run. This spike therefore converges only the worktree create/seed primitives (baseRef:"head", symlinkDirectories, sparsePaths, .worktreeinclude). It does not propose replacing session-driver-cmux with the Agent tool’s isolation:"worktree" — that path is single-turn with no attachable TTY and would destroy both terminals and escalation. Where converging an element would compromise terminals or escalation, it is ruled NO-GO with the reason documented.

The Agent-isolation-vs-cmux distinction (read this first)

Section titled “The Agent-isolation-vs-cmux distinction (read this first)”

The prior “worktree isolation pain” — CWD-drift and stale-index symptoms — was the Agent-tool isolation:"worktree" path, NOT the cmux path. These are two structurally different mechanisms that both happen to use git worktrees:

  • Agent-tool isolation:"worktree" — CC’s built-in managed worktree per sub-agent dispatch. Single-turn, no attachable terminal, the sub-agent cannot converse mid-run, and the worktree is created by CC’s internal managed-creation path (so it does honour symlinkDirectories, sparsePaths, baseRef, .worktreeinclude). Empirically present in this repo as the agent-* worktrees under .claude/worktrees/.
  • cmux (session-driver-cmux) — a full Claude session per worker, pinned to a durable, attachable cmux terminal and a worktree created by a raw git worktree add in launch-worker.sh. Because the worktree is created outside CC’s managed-creation path, none of the managed-only worktree settings apply to it. Empirically present as the subo-id-* worktrees.

The CWD-drift / stale-index issues that motivated caution were specific to the Agent path. For cmux, the equivalent failure modes were addressed by ID-27.6 (monitoring scripts resolve the events base from git rev-parse --git-common-dir via resolve_project_root(), plus the KH_CMUX_EVENTS_DIR override) and by {27.8}. So “isolation worktrees were painful” is not an argument against cmux — it is an argument against the Agent-tool path, which cmux deliberately does not use. Task ID-27’s design bypasses CC’s managed worktree-isolation structural caching precisely to keep durable terminals + mid-session escalation.

Authoritative CC facts (established this session, cited as given)

Section titled “Authoritative CC facts (established this session, cited as given)”

Verified against code.claude.com docs via claude-code-guide:

  • worktree.symlinkDirectories — MANAGED-worktree-only (claude --worktree / EnterWorktree / Agent isolation:"worktree"). NO effect on an external git worktree add.
  • worktree.sparsePaths — MANAGED-only; no effect on external git worktree add.
  • worktree.baseRef — values "fresh" (branch from origin/HEAD) or "head" (branch from local HEAD). "head" is FUNCTIONALLY EQUIVALENT to git worktree add -b <branch> <path> HEAD. Applies to managed creation only.
  • .worktreeinclude — MANAGED-only; NOT honoured for raw git worktree add; also skipped entirely if a WorktreeCreate hook is defined.
  • Agent isolation:"worktree" — single-turn, NO attachable TTY, cannot converse mid-run. Directly violates the hard constraint.
PrimitiveApplies to cmux’s raw git worktree add?Preserves durable terminals + OQ-escalation?VerdictJustification
baseRef:"head"No (setting fires on managed creation only)Yes — no change to transportKEEP-CMUXlaunch-worker.sh already does git worktree add -b "$BRANCH_NAME" "$WORKTREE_PATH" [HEAD] (lines ~195–207). With no --branch, git branches from current HEAD — behaviourally identical to baseRef:"head". The native setting only affects managed creation, so there is literally nothing to adopt; cmux’s explicit HEAD arg already matches. (The --branch <ref> flag is a superset, mapping to neither native value but covering both as needed.)
symlinkDirectoriesNo (managed-only; empirically ignored — see Evidence)N/A (cmux must solve itself; doesn’t touch transport)NO-GO (native) → KEEP-CMUXnode_modules/.venv/.bin are not symlinked into cmux worktrees because raw git worktree add is outside CC’s managed-creation path. The native setting cannot reach cmux without abandoning the raw path (which would forfeit durable terminals). cmux must implement the symlink itself if it wants the structural-caching benefit. FLAG: recommend a follow-up subtask (see Recommendations).
sparsePathsNo (managed-only)N/ANO-GO (native) → KEEP-CMUXSame managed-only limitation as symlinkDirectories. cmux workers currently get a full checkout (acceptable today — they need broad file access for spec/impl work). Manual sparse-checkout in launch-worker.sh is possible only if a future need arises; not warranted now.
.worktreeincludeNo (managed-only; skipped if a WorktreeCreate hook exists)Yes — cmux’s manual mirror runs in-process, no transport changeKEEP-CMUXlaunch-worker.sh (lines ~209–239, ID-27.4) already mirrors .worktreeinclude semantics manually: reads the project-root file, skips # comments/blanks, copies each existing literal path into the new worktree (canonical case .env.local). The native mechanism does not apply to raw git worktree add, so the manual mirror is the correct and only viable path. cmux’s mirror is literal-path-only (no glob); native is .gitignore-style — a minor semantic gap to track if globbing is ever needed.
isolation:"worktree"N/A (different mechanism entirely)No — single-turn, no attachable TTY, cannot converse mid-runNO-GOAdopting the Agent-tool isolation path would destroy durable terminals and mid-session OQ-escalation — a direct violation of the S282 hard constraint. This is the path whose CWD-drift/stale-index pain was previously observed; it is structurally unsuited to sub-orchestrators that must outlive a single turn and be attachable by the operator. KEEP cmux.
Section titled “1. Open a follow-up subtask for the node_modules/.venv symlink gap — YES”

The symlinkDirectories gap is the only primitive with a real, unaddressed cost. Because the raw-git worktree add path can never receive native symlinks, cmux must solve it itself. Recommend the Orchestrator open a follow-up subtask to add an opt-in symlink step to launch-worker.sh — e.g. for each entry in worktree.symlinkDirectories (or a cmux-local list), ln -s "$PROJECT_ROOT/<dir>" "$WORKTREE_PATH/<dir>" when the source exists. Design considerations to capture in that subtask:

  • Trade-off already documented in SKILL.md “Known limitations”: symlinks save disk + avoid per-worker bun install / pip install, but reintroduce ?? artefacts in git status that the dirty-tree safety gate in stop-worker.sh (and orphan-sweep) must then exclude. A symlinked node_modules shows as untracked unless .gitignore already ignores it (it does in this repo) — verify the safety gate stays clean.
  • Scope it as opt-in (flag or config-driven) so research-only / doc-only workers that need no tooling keep the clean full-checkout default.
  • Cheap win: reuse the existing worktree.symlinkDirectories list in settings.local.json (["node_modules", ".venv", ".bin"]) as the source of truth so cmux and the managed path converge on the same intent even though the mechanism differs.

This is a genuine implementation subtask (touches launch-worker.sh), out of scope for this investigation-only spike.

2. No follow-up needed for baseRef, sparsePaths, .worktreeinclude

Section titled “2. No follow-up needed for baseRef, sparsePaths, .worktreeinclude”
  • baseRef:"head" — already matched; no work.
  • sparsePaths — no current need; full checkout is correct for cmux workers.
  • .worktreeinclude — already mirrored manually (ID-27.4); working. Optionally note the literal-path-vs-glob semantic gap in the SKILL.md if globbing is ever requested, but no action required now.

Permanently NO-GO under the S282 hard constraint. Worth a one-line note in SKILL.md (the “When to invoke” section already states this) so future readers do not re-litigate it.

Read-only inspection of existing worktrees under .claude/worktrees/ (no worktrees created, destroyed, or disturbed):

  • cmux worker worktrees (subo-id-48, subo-id-56-8, subo-id-63): all three have node_modules ABSENT, .venv ABSENT, node_modules/.bin ABSENT — neither symlinks nor real dirs. Confirms the gap is real: cmux workers get a full git checkout with no shared/symlinked tooling.
  • Main root (/Users/liamj/Documents/development/knowledge-hub): real node_modules (1159 entries) — a full install, as expected for the parent.
  • Critical proof of managed-only behaviour: settings.local.json does configure worktree.symlinkDirectories: ["node_modules", ".venv", ".bin"] (lines 27–33). Despite this, the cmux subo-id-* worktrees have none of those dirs. This is direct evidence that the native symlinkDirectories setting has zero effect on cmux’s raw git worktree add path — it fires only for CC-managed worktree creation. The setting is present and silently ignored by cmux.
  • Agent-isolation worktree (agent-a8204c1e0f15826c0): has a small node_modules (only a .vite build-cache entry, 96 bytes) — i.e. the managed path produces a different node_modules shape than the parent’s full install. (No full symlink tree observed in this particular instance, likely because that agent run did not exercise the symlinked dir; the structural point — that the managed path can populate node_modules while the cmux path cannot — holds.)

Per the spike constraints this is a .md/research-only deliverable; the GitNexus / ast-dataflow impact-analysis discipline does not apply (no symbol modification). Grounding was via direct file reads of launch-worker.sh, SKILL.md, settings.local.json, .worktreeinclude, .gitignore, and read-only filesystem inspection.