Files
msd-core/docs/how-to/fix-worktree-base-mismatch.md
Tom Boucher cf8bd3cd5e fix(#683): auto-degrade phase execution to sequential on worktree base mismatch (#749)
* fix(#683): auto-degrade phase execution to sequential on worktree base mismatch

Claude Code forks worktree-isolated executors off the repository default
branch (origin/HEAD), not the orchestrator's HEAD. Running /gsd-execute-phase
on a branch diverged from the default (unmerged milestone/feature branch) left
every executor without the phase's plan files and tripped the
worktree-branch-check guard with `exit 42` — 100% reproducible, all OSes.

- New module src/worktree-base-ref.cts: HEAD-vs-fork-base drift detection
  (origin/HEAD with symbolic-ref fallback) and no-clobber worktree.baseRef
  management, exposed as `worktree base-check` / `worktree set-baseref`.
- execute-phase.md: pre-dispatch, for Claude Code with worktrees enabled,
  auto-degrades the run to sequential on the main tree when a base mismatch
  is detected, recommending worktree.baseRef:"head". The exit-42 guard stays
  as a backstop.
- Installer: fresh local Claude installs set worktree.baseRef:"head" in
  .claude/settings.local.json (no-clobber, respecting an explicit shared
  settings.json value); upgrades print an opt-in notice pointing at
  `gsd-tools worktree set-baseref`.
- Docs: how-to guide, CLI/config reference, planning-config cross-ref.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#683): auto-apply worktree.baseRef on upgrade; gate fresh+upgrade on use_worktrees

Per maintainer direction: on a local Claude Code UPGRADE, set
worktree.baseRef:"head" automatically (no opt-in notice) when the project's
workflow.use_worktrees is enabled, instead of merely printing a remediation
notice. For consistency the FRESH path is now gated the same way: both paths
compute worktrees-enabled once (bounded walk-up read of .planning/config.json,
default enabled unless workflow.use_worktrees === false) and apply the
no-clobber baseRef only when enabled — never overwriting an explicit value in
settings.local.json or a shared settings.json. gsd-tools worktree set-baseref
remains for manual use. Docs + changeset updated; tests hardened (file-exists
assertions, fresh+disabled case, upgrade idempotency).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#683): measure workflow byte-budget on LF, fixing Windows-only CI failure

The workflow-size-budget test failed only on Windows: git checks out the .md
files as CRLF (no eol=lf in .gitattributes) and byteCount used
fs.statSync().size (raw on-disk bytes), counting an extra \r per line. That
inflated execute-phase.md — the XL high-water-mark file pinned near its ceiling
by the tighten-only ratchet — from 88492 LF bytes to ~90245 on Windows, over
the 90000 XL ceiling, while passing on the LF-checkout Mac/Linux runners.

The ceilings are explicitly "calibrated against raw `wc -c`" on an LF checkout,
so the measurement should be LF-based on every platform. byteCount now reads the
file and counts Buffer.byteLength after stripping CR, making the budget
platform-independent (a no-op on LF checkouts; verified statSync === normalized
for all 88 workflow files). No ceilings changed. Added a regression test
asserting CRLF and LF content of the same file count identically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#683): make worktree-base-ref test path mocks Windows-safe (path.join)

tests/worktree-base-ref.test.cjs keyed its injected readFile/writeFile mocks
(and a few expected `file` values) with forward-slash template literals like
`${claudeDir}/settings.local.json`. The module composes those paths with
path.join(), which emits backslashes on Windows, so the mock keys never matched
the module's lookup → readFile returned null → resolveEffectiveBaseRef /
cmdWorktreeBaseCheck / cmdWorktreeSetBaseRef (and the JSONC variants) failed on
the Windows full-test runner only (they passed on Mac/Linux, and the install
tests passed because they use the real filesystem). The module is correct;
only the test fixtures hardcoded '/'.

All mock keys and path assertions now use path.join(base, ...) mirroring the
module, so they match on every platform (no-op on POSIX). 19 path references
across 16 lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 23:40:24 -04:00

6.1 KiB

How to fix the worktree base-mismatch (exit 42) error

Goal: Understand why /gsd-execute-phase halts with FATAL: worktree base mismatch / exit 42 when your branch is ahead of the default branch, and choose the right fix to restore normal — or parallel — execution.

Prerequisites: GSD Core is installed and you have an active project. You have run /gsd-execute-phase and either seen the exit-42 error or the one-line ⚠ Worktree base mismatch warning.


What you will see

When you run /gsd-execute-phase on a branch that is ahead of the repository's default branch (for example, an unmerged milestone branch, a long-lived feature branch, or a branch with commits not yet in origin/HEAD), you may see one of two messages:

Automatic-degrade warning (phase still completes):

⚠ Worktree base mismatch: HEAD (abc12345) differs from origin/HEAD (def67890).
Running this phase sequentially on the main working tree.
To keep parallel worktrees, set worktree.baseRef:"head" in
.claude/settings.local.json (or run: gsd-tools worktree set-baseref). See #683.

The phase runs to completion sequentially; nothing is blocked. This is the runtime mitigation.

Exit-42 halt (older installs or misconfigured environments):

FATAL: worktree base mismatch

All worktree-isolated executors halt immediately. Zero progress is made.


Why this happens

Claude Code's isolation="worktree" forks executor worktrees from the repository's default branch (origin/HEAD), not from your current HEAD. When your branch contains commits that origin/HEAD does not have — plan files, new source files, anything added since the branch diverged — those files are absent inside each worktree. GSD's worktree-branch-check safety guard correctly refuses to act on a worktree that does not match the orchestrator's state, and exits with code 42.

This is the guard working as designed: it prevents silent data loss or phantom edits in the wrong tree. The error is a branch-state condition, not an OS-specific or hardware issue.


Option 1 — Do nothing (you are already unblocked)

If you saw the ⚠ Worktree base mismatch warning rather than an exit-42 halt, GSD has already automatically degraded to sequential execution on the main working tree for this run. The phase will complete. No action is required.

Use this option when:

  • You are on a diverged branch temporarily
  • You do not care about parallel execution for this phase
  • You want to merge back to the default branch soon

This option restores parallel worktree execution on diverged branches. It tells Claude Code to fork executor worktrees from your current HEAD instead of origin/HEAD, so the plan files and branch-only commits are present in every worktree.

Run the convenience command from your project root:

node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" worktree set-baseref

This writes worktree.baseRef: "head" into .claude/settings.local.json in your project root. It is no-clobber: if you already have an explicit baseRef set to something else, it leaves your value in place and tells you.

To verify the result:

node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" worktree base-check

The output is JSON. When shouldDegrade is false and reason is "baseref-head", parallel worktrees will work on any branch.

Alternatively, set the value by hand in .claude/settings.local.json:

{
  "worktree": {
    "baseRef": "head"
  }
}

Note: Fresh installs and upgrades of GSD Core both set worktree.baseRef:"head" automatically in .claude/settings.local.json (no-clobber) when workflow.use_worktrees is enabled (the default). You can also apply or re-apply it manually at any time with gsd-tools worktree set-baseref — for example, if you toggled worktrees on after the initial install.

Use this option when:

  • You regularly work on long-lived or milestone branches
  • You want parallel phase execution (faster, lower context-window pressure)
  • You are a solo developer or team working on a feature branch for an extended period

Option 3 — Fallback: disable worktrees entirely

If worktrees are causing persistent problems beyond the base-mismatch (for example, your environment does not support them), disable them permanently for this project:

Add or edit .planning/config.json:

{
  "workflow": {
    "use_worktrees": false
  }
}

All executor agents will then run sequentially on the main working tree for every phase. This is equivalent to what the automatic degrade does, but permanent.

Use this option when:

  • Worktrees are consistently problematic in your environment
  • You prefer sequential execution for auditability or tooling reasons
  • You are on a platform or CI setup that does not support git worktrees

See also: workflow.use_worktrees in the configuration reference.


The exit-42 backstop

The worktree-branch-check guard (exit 42) remains active in all execution modes as a safety backstop. It fires only when an executor worktree's branch does not match the expected orchestrator state — a condition that should not arise once you have applied one of the options above. If you continue to see exit 42 after setting worktree.baseRef: "head", run /gsd-forensics to investigate.


Summary

Situation Recommended action
Saw the warning, phase completed Nothing — degrade handled it automatically
Regularly on diverged branches, want parallel execution worktree set-baseref (Option 2)
Worktrees consistently problematic Set workflow.use_worktrees: false (Option 3)
Still seeing exit 42 after fixes Run /gsd-forensics "exit 42 after fix"