fix: have executor self-report worktree metadata (#1349)

This commit is contained in:
Tom Boucher
2026-06-16 14:19:08 -04:00
committed by GitHub
parent 6e242bd76a
commit 9540fe43b9
6 changed files with 55 additions and 3 deletions

View File

@@ -0,0 +1,6 @@
---
type: Fixed
pr: 1349
---
**Parallel worktree execution now has executor-authored cleanup metadata** — executor agents capture their worktree path, branch, and expected base before task commits and return a parseable metadata block for execute-phase to prefer over runtime harness metadata. (#1297)

View File

@@ -103,6 +103,24 @@ PLAN_START_EPOCH=$(date +%s)
```
</step>
<worktree_metadata_capture>
If running inside a git worktree, capture authoritative worktree identity before
any task commit changes HEAD. The execute-phase orchestrator consumes this from
your final `<worktree_metadata>` return block to build the wave cleanup manifest
without relying on runtime harness metadata (#1297).
```bash
GSD_WORKTREE_PATH=""
GSD_WORKTREE_BRANCH=""
GSD_WORKTREE_EXPECTED_BASE=""
if [ -f .git ]; then
GSD_WORKTREE_PATH=$(git rev-parse --show-toplevel)
GSD_WORKTREE_BRANCH=$(git rev-parse --abbrev-ref HEAD)
GSD_WORKTREE_EXPECTED_BASE=$(git rev-parse HEAD)
fi
```
</worktree_metadata_capture>
<step name="determine_execution_pattern">
```bash
grep -n "type=\"checkpoint" [plan-path]
@@ -761,6 +779,10 @@ into the user's project history.
**Tasks:** {completed}/{total}
**SUMMARY:** {path to SUMMARY.md}
<worktree_metadata>
{"agent_id":"{phase}-{plan}","worktree_path":"${GSD_WORKTREE_PATH:-}","branch":"${GSD_WORKTREE_BRANCH:-}","expected_base":"${GSD_WORKTREE_EXPECTED_BASE:-}"}
</worktree_metadata>
**Commits:**
- {hash}: {message}
- {hash}: {message}

View File

@@ -635,6 +635,7 @@ increases monotonically across waves. `{status}` is `complete` (success),
this commit — the orchestrator force-removes the worktree after you return, and
any uncommitted SUMMARY.md will be permanently lost (#2070).
REQUIRED ORDER: Write SUMMARY.md → commit → only then any narration. No text between Write and commit (truncation risk; #2070 rescue is not primary defense).
</parallel_execution>
<execution_context>
@@ -682,7 +683,7 @@ increases monotonically across waves. `{status}` is `complete` (success),
)
```
Immediately after each worktree `Agent()` spawn returns metadata, atomically append `{agent_id, worktree_path, branch, expected_base}` to `WAVE_WORKTREE_MANIFEST`. If any field is missing, stop and ask for recovery instead of scanning all agent worktrees.
After each `Agent()` returns, parse executor-returned worktree metadata (`<worktree_metadata>`) before harness metadata, then atomically append `{agent_id, worktree_path, branch, expected_base}` to `WAVE_WORKTREE_MANIFEST`. Missing: stop and ask for recovery instead of scanning worktrees.
> **Worktree recovery policy (#48 + #1292):** See `execute-phase/steps/worktree-recovery-policy.md` — FAIL-CLOSED rule for base/HEAD-namespace mismatches AND isolated-run fail-safe recovery.

View File

@@ -14,7 +14,7 @@
"gsd-domain-researcher.md": 6938,
"gsd-eval-auditor.md": 7761,
"gsd-eval-planner.md": 7008,
"gsd-executor.md": 42519,
"gsd-executor.md": 43343,
"gsd-framework-selector.md": 6778,
"gsd-integration-checker.md": 15148,
"gsd-intel-updater.md": 18122,

View File

@@ -24,7 +24,7 @@
"docs-update.md": 54770,
"edit-phase.md": 12883,
"eval-review.md": 9923,
"execute-phase.md": 93122,
"execute-phase.md": 93157,
"execute-plan.md": 31365,
"explore.md": 10497,
"extract-learnings.md": 12849,

View File

@@ -684,6 +684,29 @@ describe('bug #3384: worktree cleanup workflow contracts', () => {
assert.doesNotMatch(content, /done < <\(git worktree list --porcelain \| grep "\^worktree " \| grep "\\\.claude\/worktrees\/agent-"/);
});
test('#1297 gsd-executor self-reports authoritative worktree metadata', () => {
const content = fs.readFileSync(EXECUTOR_AGENT_PATH, 'utf8');
assert.match(content, /<worktree_metadata_capture>/);
assert.match(content, /git rev-parse --show-toplevel/);
assert.match(content, /git rev-parse --abbrev-ref HEAD/);
assert.match(content, /GSD_WORKTREE_EXPECTED_BASE=\$\(git rev-parse HEAD\)/);
assert.match(content, /<worktree_metadata>/);
assert.match(content, /"worktree_path":/);
assert.match(content, /"branch":/);
assert.match(content, /"expected_base":/);
});
test('#1297 execute-phase consumes executor-returned worktree metadata before harness metadata', () => {
const content = fs.readFileSync(EXECUTE_PHASE_PATH, 'utf8');
assert.match(content, /<worktree_metadata>/);
assert.match(content, /executor-returned worktree metadata/i);
assert.match(content, /harness metadata/i);
assert.ok(
content.indexOf('executor-returned worktree metadata') < content.indexOf('harness metadata'),
'execute-phase must prefer executor-returned worktree metadata before runtime harness metadata (#1297)'
);
});
test('quick contract requires a cleanup manifest instead of global worktree discovery', () => {
const content = fs.readFileSync(QUICK_PATH, 'utf8');
assert.match(content, /WAVE_WORKTREE_MANIFEST|QUICK_WORKTREE_MANIFEST/);