Merge pull request #1991 from jslitzkerttcu/fix/1941-quick-worktree-stale-base
fix(#1941): degrade /gsd-quick worktree dispatch when fork base is stale
This commit is contained in:
5
.changeset/eager-tunas-swim.md
Normal file
5
.changeset/eager-tunas-swim.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 1991
|
||||
---
|
||||
**`/gsd-quick` no longer halts with a stale-base worktree mismatch** — the worktree executor now degrades to sequential execution when its fork base has diverged from origin/HEAD, instead of spawning a worktree guaranteed to fail the base-mismatch guard.
|
||||
@@ -1,16 +1,16 @@
|
||||
# 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.
|
||||
**Goal:** Understand why `/gsd-execute-phase` or `/gsd-quick` 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.
|
||||
**Prerequisites:** GSD Core is installed and you have an active project. You have run `/gsd-execute-phase` or `/gsd-quick` 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:
|
||||
When you run `/gsd-execute-phase` or `/gsd-quick` 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):**
|
||||
**Automatic-degrade warning (phase or quick task still completes):**
|
||||
|
||||
```
|
||||
⚠ Worktree base mismatch: HEAD (abc12345) differs from origin/HEAD (def67890).
|
||||
@@ -19,7 +19,7 @@ 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.
|
||||
The phase or quick task runs to completion sequentially; nothing is blocked. This is the runtime mitigation (`/gsd-execute-phase`: #683/#1369; `/gsd-quick`: #1941).
|
||||
|
||||
**Exit-42 halt (older installs or misconfigured environments):**
|
||||
|
||||
|
||||
@@ -671,6 +671,26 @@ fi
|
||||
|
||||
**Step 6: Spawn executor**
|
||||
|
||||
Auto-degrade to sequential if HEAD has diverged from the worktree fork base (#1941, mirrors
|
||||
execute-phase's #683/#1369 guard). Claude Code's `isolation="worktree"` forks new worktrees from
|
||||
`origin/HEAD`, not the live local HEAD. If a prior quick task in this session (or the Step 5.6
|
||||
pre-dispatch plan commit above) advanced local HEAD without an intervening `git push`,
|
||||
`origin/HEAD` stays pinned to a stale ancestor and the executor's `worktree_branch_check` guard
|
||||
halts with a base-mismatch fatal — potentially many commits behind, not just one. Run this check
|
||||
immediately before capturing `EXPECTED_BASE` so it reflects the most current local state.
|
||||
|
||||
```bash
|
||||
if [ "$RUNTIME" = "claude" ] && [ "${USE_WORKTREES:-true}" != "false" ]; then
|
||||
_QUICK_SHOULD_DEGRADE=$(gsd_run query worktree.base-check --pick shouldDegrade 2>/dev/null || true)
|
||||
if [ "$_QUICK_SHOULD_DEGRADE" = "true" ]; then
|
||||
_QUICK_DEGRADE_MSG=$(gsd_run query worktree.base-check --pick message 2>/dev/null || true)
|
||||
[ -n "$_QUICK_DEGRADE_MSG" ] && printf '%s\n' "$_QUICK_DEGRADE_MSG" >&2
|
||||
echo "⚠ [#1941] Worktree fork base diverged from orchestrator HEAD — auto-degrading to sequential mode for this quick task to avoid a base-mismatch halt." >&2
|
||||
USE_WORKTREES=false
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
Capture current HEAD before spawning (used for worktree branch check):
|
||||
```bash
|
||||
EXPECTED_BASE=$(git rev-parse HEAD)
|
||||
|
||||
76
tests/fix-1941-quick-worktree-stale-base.test.cjs
Normal file
76
tests/fix-1941-quick-worktree-stale-base.test.cjs
Normal file
@@ -0,0 +1,76 @@
|
||||
// allow-test-rule: source-text-is-the-product #1941
|
||||
// Workflow .md files are the installed AI instructions — their text IS what the runtime
|
||||
// loads. Testing text content tests the deployed contract. Per CONTRIBUTING.md exception matrix.
|
||||
|
||||
/**
|
||||
* Regression tests for bug #1941: /gsd-quick worktree executor forks from a stale base —
|
||||
* up to many commits behind, not just the one-commit gap #1265 already covers.
|
||||
*
|
||||
* Root cause: Claude Code's isolation="worktree" forks new worktrees from origin/HEAD, not
|
||||
* the live local HEAD. When prior local commits (e.g. earlier quick tasks in the same
|
||||
* session, or this task's own Step 5.6 pre-dispatch plan commit) advance local HEAD without
|
||||
* an intervening `git push`, origin/HEAD stays pinned to a stale ancestor and the executor's
|
||||
* worktree_branch_check guard halts with a base-mismatch fatal. The fix ports the
|
||||
* worktree.base-check auto-degrade pattern already used by execute-phase (#683/#1369) into
|
||||
* quick.md's single-dispatch path.
|
||||
*/
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const WORKFLOW_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'quick.md');
|
||||
|
||||
describe('quick: pre-dispatch worktree base re-check (#1941)', () => {
|
||||
test('workflow file exists', () => {
|
||||
assert.ok(fs.existsSync(WORKFLOW_PATH), 'workflows/quick.md should exist');
|
||||
});
|
||||
|
||||
test('Step 6 runs worktree.base-check before capturing EXPECTED_BASE', () => {
|
||||
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
|
||||
const step6Idx = content.indexOf('**Step 6: Spawn executor**');
|
||||
const baseCheckIdx = content.indexOf('worktree.base-check', step6Idx);
|
||||
const expectedBaseIdx = content.indexOf('EXPECTED_BASE=$(git rev-parse HEAD)', step6Idx);
|
||||
assert.ok(step6Idx !== -1, '"Step 6: Spawn executor" must exist in quick.md');
|
||||
assert.ok(baseCheckIdx !== -1, 'worktree.base-check must be invoked within Step 6');
|
||||
assert.ok(expectedBaseIdx !== -1, 'EXPECTED_BASE capture must exist within Step 6');
|
||||
assert.ok(
|
||||
baseCheckIdx < expectedBaseIdx,
|
||||
'worktree.base-check must run BEFORE EXPECTED_BASE is captured so the degrade decision reflects the most current local HEAD'
|
||||
);
|
||||
});
|
||||
|
||||
test('degrade check references #1941 for traceability', () => {
|
||||
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
|
||||
assert.ok(content.includes('#1941'), 'quick.md must reference #1941');
|
||||
});
|
||||
|
||||
test('degrade check sets USE_WORKTREES=false when shouldDegrade is true', () => {
|
||||
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
|
||||
const baseCheckIdx = content.indexOf('worktree.base-check');
|
||||
const block = content.slice(baseCheckIdx, baseCheckIdx + 600);
|
||||
assert.ok(
|
||||
block.includes('shouldDegrade') && block.includes('USE_WORKTREES=false'),
|
||||
'degrade check must override USE_WORKTREES=false when shouldDegrade is true'
|
||||
);
|
||||
});
|
||||
|
||||
test('degrade check guards on RUNTIME=claude (worktree isolation is Claude Code-specific)', () => {
|
||||
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
|
||||
const baseCheckIdx = content.indexOf('worktree.base-check');
|
||||
const block = content.slice(Math.max(0, baseCheckIdx - 200), baseCheckIdx + 200);
|
||||
assert.ok(
|
||||
block.includes('RUNTIME') && (block.includes('"claude"') || block.includes("'claude'")),
|
||||
'degrade check must guard on RUNTIME=claude'
|
||||
);
|
||||
});
|
||||
|
||||
test('degrade check names origin/HEAD as the stale fork base', () => {
|
||||
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
|
||||
const step6Idx = content.indexOf('**Step 6: Spawn executor**');
|
||||
const nextSection = content.indexOf('\n---', step6Idx);
|
||||
const section = content.slice(step6Idx, nextSection === -1 ? undefined : nextSection);
|
||||
assert.ok(section.includes('origin/HEAD'), 'Step 6 must name origin/HEAD as the stale fork base');
|
||||
});
|
||||
});
|
||||
@@ -274,7 +274,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "dc5598ae8accdecd",
|
||||
"gsd-core/workflows/profile-user.md": "355af92ac285567f",
|
||||
"gsd-core/workflows/progress.md": "79a11ce798082054",
|
||||
"gsd-core/workflows/quick.md": "a6aa362881f4e5da",
|
||||
"gsd-core/workflows/quick.md": "35582887917ef938",
|
||||
"gsd-core/workflows/reapply-patches.md": "4dcd6117d0a507ca",
|
||||
"gsd-core/workflows/remove-phase.md": "23b9eb0858a2535e",
|
||||
"gsd-core/workflows/remove-workspace.md": "d0bd7e0601138798",
|
||||
|
||||
@@ -344,7 +344,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b",
|
||||
"gsd-core/workflows/profile-user.md": "14263db831230142",
|
||||
"gsd-core/workflows/progress.md": "893aa3c36983f74b",
|
||||
"gsd-core/workflows/quick.md": "3bafc0a59cc90d26",
|
||||
"gsd-core/workflows/quick.md": "5790ceb09aa685be",
|
||||
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
|
||||
@@ -273,7 +273,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "ab157cd8e49621dd",
|
||||
"gsd-core/workflows/profile-user.md": "ff3820a27731ceb8",
|
||||
"gsd-core/workflows/progress.md": "bd1ecf9207331bda",
|
||||
"gsd-core/workflows/quick.md": "bd56a6313d5ed43c",
|
||||
"gsd-core/workflows/quick.md": "da83b1a15b7f1bf7",
|
||||
"gsd-core/workflows/reapply-patches.md": "ba9406b60f2c4041",
|
||||
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
|
||||
"gsd-core/workflows/remove-workspace.md": "f3ab3a88a7e9e1ed",
|
||||
|
||||
@@ -277,7 +277,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "9923878a4f6a2d91",
|
||||
"gsd-core/workflows/profile-user.md": "26f74db0a7fcd268",
|
||||
"gsd-core/workflows/progress.md": "9f326d63afb4b76b",
|
||||
"gsd-core/workflows/quick.md": "10051a9b41d5d210",
|
||||
"gsd-core/workflows/quick.md": "68a9dfcd53f0859f",
|
||||
"gsd-core/workflows/reapply-patches.md": "eb4272145a117904",
|
||||
"gsd-core/workflows/remove-phase.md": "e336350f8113a328",
|
||||
"gsd-core/workflows/remove-workspace.md": "e685dfbd736dfd90",
|
||||
|
||||
@@ -344,7 +344,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b",
|
||||
"gsd-core/workflows/profile-user.md": "4fa910d15dea5695",
|
||||
"gsd-core/workflows/progress.md": "893aa3c36983f74b",
|
||||
"gsd-core/workflows/quick.md": "5e7dcdd864513b87",
|
||||
"gsd-core/workflows/quick.md": "9033dbe58443af36",
|
||||
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "d13e1cc81de40896",
|
||||
"gsd-core/workflows/profile-user.md": "05828c8cc61ef384",
|
||||
"gsd-core/workflows/progress.md": "7bedc431bb55edb8",
|
||||
"gsd-core/workflows/quick.md": "85fb48e52f0b3725",
|
||||
"gsd-core/workflows/quick.md": "3e7686705da2af19",
|
||||
"gsd-core/workflows/reapply-patches.md": "26297b84736e66a4",
|
||||
"gsd-core/workflows/remove-phase.md": "9ee0fddd11a0d9d4",
|
||||
"gsd-core/workflows/remove-workspace.md": "19d7465aaa50cb62",
|
||||
|
||||
@@ -275,7 +275,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "2833905f119b5722",
|
||||
"gsd-core/workflows/profile-user.md": "5cc032206c99ef71",
|
||||
"gsd-core/workflows/progress.md": "6b9a84a43dc55af5",
|
||||
"gsd-core/workflows/quick.md": "89c34e07f1bf6f53",
|
||||
"gsd-core/workflows/quick.md": "7ddfe17f048541ec",
|
||||
"gsd-core/workflows/reapply-patches.md": "8fd59e24b486f180",
|
||||
"gsd-core/workflows/remove-phase.md": "e262654e319d1bc4",
|
||||
"gsd-core/workflows/remove-workspace.md": "ceddfeef5f2d6754",
|
||||
|
||||
@@ -344,7 +344,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "c67d90c65da47168",
|
||||
"gsd-core/workflows/profile-user.md": "8c943983241260b5",
|
||||
"gsd-core/workflows/progress.md": "65aabee5e8a6dd82",
|
||||
"gsd-core/workflows/quick.md": "db98fdbe79d68ffc",
|
||||
"gsd-core/workflows/quick.md": "762256cf6d177c06",
|
||||
"gsd-core/workflows/reapply-patches.md": "ba9406b60f2c4041",
|
||||
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
|
||||
"gsd-core/workflows/remove-workspace.md": "433affcd1a200826",
|
||||
|
||||
@@ -274,7 +274,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "ecabd55e4eabf229",
|
||||
"gsd-core/workflows/profile-user.md": "de5030437226cf2c",
|
||||
"gsd-core/workflows/progress.md": "f18db000584d9cb1",
|
||||
"gsd-core/workflows/quick.md": "66b26d6fe2bd4d71",
|
||||
"gsd-core/workflows/quick.md": "5044ce4e7512e174",
|
||||
"gsd-core/workflows/reapply-patches.md": "158083a310859594",
|
||||
"gsd-core/workflows/remove-phase.md": "fce799aae3ab2715",
|
||||
"gsd-core/workflows/remove-workspace.md": "8facde381657dd71",
|
||||
|
||||
@@ -344,7 +344,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "ab157cd8e49621dd",
|
||||
"gsd-core/workflows/profile-user.md": "203ebe3f8f3876a8",
|
||||
"gsd-core/workflows/progress.md": "9381c59676ccb937",
|
||||
"gsd-core/workflows/quick.md": "11ba83f6350462fb",
|
||||
"gsd-core/workflows/quick.md": "fd7461e5a92fe450",
|
||||
"gsd-core/workflows/reapply-patches.md": "becf9728cdb124c4",
|
||||
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
|
||||
"gsd-core/workflows/remove-workspace.md": "fc83f362a2d0a1b7",
|
||||
|
||||
@@ -310,7 +310,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b",
|
||||
"gsd-core/workflows/profile-user.md": "5abfae83739fa978",
|
||||
"gsd-core/workflows/progress.md": "893aa3c36983f74b",
|
||||
"gsd-core/workflows/quick.md": "d722803b72217377",
|
||||
"gsd-core/workflows/quick.md": "e265e01cfe117215",
|
||||
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
|
||||
@@ -344,7 +344,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "929b7cb0c99c7b9e",
|
||||
"gsd-core/workflows/profile-user.md": "248d59a31948e0ed",
|
||||
"gsd-core/workflows/progress.md": "8fc3404087f50b95",
|
||||
"gsd-core/workflows/quick.md": "3866a5259e8912d8",
|
||||
"gsd-core/workflows/quick.md": "10cc8ddd4bc3f0ee",
|
||||
"gsd-core/workflows/reapply-patches.md": "a0e9b53f90abceb2",
|
||||
"gsd-core/workflows/remove-phase.md": "dea4661e8f89596f",
|
||||
"gsd-core/workflows/remove-workspace.md": "446847e71aa52504",
|
||||
|
||||
@@ -274,7 +274,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "cef0f65b16d500b4",
|
||||
"gsd-core/workflows/profile-user.md": "263c0693563d98da",
|
||||
"gsd-core/workflows/progress.md": "3b1b2142a74af85c",
|
||||
"gsd-core/workflows/quick.md": "518740e30dcd4da3",
|
||||
"gsd-core/workflows/quick.md": "0a9a7dacc73f8e53",
|
||||
"gsd-core/workflows/reapply-patches.md": "de0ee8acfe7245b2",
|
||||
"gsd-core/workflows/remove-phase.md": "e8ae4fbbfac700f0",
|
||||
"gsd-core/workflows/remove-workspace.md": "4ac64de862dc650e",
|
||||
|
||||
@@ -274,7 +274,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "79fd55b88ea2db9c",
|
||||
"gsd-core/workflows/profile-user.md": "672821e6b1266645",
|
||||
"gsd-core/workflows/progress.md": "94768f835b0b8908",
|
||||
"gsd-core/workflows/quick.md": "8ac5f3919f754499",
|
||||
"gsd-core/workflows/quick.md": "9bbf907e39688638",
|
||||
"gsd-core/workflows/reapply-patches.md": "21b38c374f19fd78",
|
||||
"gsd-core/workflows/remove-phase.md": "a46c2fe853bf4e86",
|
||||
"gsd-core/workflows/remove-workspace.md": "ae0e1c6d4438d663",
|
||||
|
||||
@@ -274,7 +274,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "acd59f915d018ad4",
|
||||
"gsd-core/workflows/profile-user.md": "c4313672b81b5bcd",
|
||||
"gsd-core/workflows/progress.md": "18813a345bd2343a",
|
||||
"gsd-core/workflows/quick.md": "0fcad72e7dbe99c9",
|
||||
"gsd-core/workflows/quick.md": "3acd391cc0c3813a",
|
||||
"gsd-core/workflows/reapply-patches.md": "d449a23d3acf6379",
|
||||
"gsd-core/workflows/remove-phase.md": "e7a6af429b36e77b",
|
||||
"gsd-core/workflows/remove-workspace.md": "b5e60fbb33b3e33a",
|
||||
|
||||
@@ -344,7 +344,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b",
|
||||
"gsd-core/workflows/profile-user.md": "e23bea0a69c0bb4b",
|
||||
"gsd-core/workflows/progress.md": "893aa3c36983f74b",
|
||||
"gsd-core/workflows/quick.md": "1bc59c4d97fed876",
|
||||
"gsd-core/workflows/quick.md": "5747c2d8a37affbb",
|
||||
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
"pr-branch.md": 15963,
|
||||
"profile-user.md": 21246,
|
||||
"progress.md": 30599,
|
||||
"quick.md": 49183,
|
||||
"quick.md": 50452,
|
||||
"reapply-patches.md": 20312,
|
||||
"remove-phase.md": 8513,
|
||||
"remove-workspace.md": 7551,
|
||||
|
||||
Reference in New Issue
Block a user