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.
Question
Section titled “Question”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 honoursymlinkDirectories,sparsePaths,baseRef,.worktreeinclude). Empirically present in this repo as theagent-*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 rawgit worktree addinlaunch-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 thesubo-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/ Agentisolation:"worktree"). NO effect on an externalgit worktree add.worktree.sparsePaths— MANAGED-only; no effect on externalgit worktree add.worktree.baseRef— values"fresh"(branch fromorigin/HEAD) or"head"(branch from local HEAD)."head"is FUNCTIONALLY EQUIVALENT togit worktree add -b <branch> <path> HEAD. Applies to managed creation only..worktreeinclude— MANAGED-only; NOT honoured for rawgit worktree add; also skipped entirely if aWorktreeCreatehook is defined.- Agent
isolation:"worktree"— single-turn, NO attachable TTY, cannot converse mid-run. Directly violates the hard constraint.
Per-primitive verdict table
Section titled “Per-primitive verdict table”| Primitive | Applies to cmux’s raw git worktree add? | Preserves durable terminals + OQ-escalation? | Verdict | Justification |
|---|---|---|---|---|
baseRef:"head" | No (setting fires on managed creation only) | Yes — no change to transport | KEEP-CMUX | launch-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.) |
symlinkDirectories | No (managed-only; empirically ignored — see Evidence) | N/A (cmux must solve itself; doesn’t touch transport) | NO-GO (native) → KEEP-CMUX | node_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). |
sparsePaths | No (managed-only) | N/A | NO-GO (native) → KEEP-CMUX | Same 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. |
.worktreeinclude | No (managed-only; skipped if a WorktreeCreate hook exists) | Yes — cmux’s manual mirror runs in-process, no transport change | KEEP-CMUX | launch-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-run | NO-GO | Adopting 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. |
Recommendations
Section titled “Recommendations”1. Open a follow-up subtask for the node_modules/.venv symlink gap — YES
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 ingit statusthat the dirty-tree safety gate instop-worker.sh(and orphan-sweep) must then exclude. A symlinkednode_modulesshows as untracked unless.gitignorealready 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.symlinkDirectorieslist insettings.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.
3. Do NOT adopt isolation:"worktree"
Section titled “3. Do NOT adopt isolation:"worktree"”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.
Evidence note (empirical check)
Section titled “Evidence note (empirical check)”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 havenode_modulesABSENT,.venvABSENT,node_modules/.binABSENT — 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): realnode_modules(1159 entries) — a full install, as expected for the parent. - Critical proof of managed-only behaviour:
settings.local.jsondoes configureworktree.symlinkDirectories: ["node_modules", ".venv", ".bin"](lines 27–33). Despite this, the cmuxsubo-id-*worktrees have none of those dirs. This is direct evidence that the nativesymlinkDirectoriessetting has zero effect on cmux’s rawgit worktree addpath — it fires only for CC-managed worktree creation. The setting is present and silently ignored by cmux. - Agent-isolation worktree (
agent-a8204c1e0f15826c0): has a smallnode_modules(only a.vitebuild-cache entry, 96 bytes) — i.e. the managed path produces a differentnode_modulesshape 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 populatenode_moduleswhile the cmux path cannot — holds.)
Code-intel note
Section titled “Code-intel note”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.