Regression: regenerateIfMissing(PLAN) no-op when task plans missing → plan-slice stuck loop
Problem
Auto-mode stuck-loops on plan-slice when any tasks/T##-PLAN.md is missing from disk, even though the slice was successfully planned in the DB. The artifact-verification recovery path (regenerateIfMissing(..., "PLAN")) is a no-op for this exact failure mode. This is a regression of #6146 / #6343.
Root Cause
workflow-projections.js:385-464 (the regenerateIfMissing function) has two defects in the "PLAN" branch:
Early-return ignores task plans. At
workflow-projections.js:441:if (existsSync(filePath)) { // filePath = ${sliceId}-PLAN.md only return false; }If the slice plan exists but any
tasks/T##-PLAN.mdis missing (the verifier's actual failure), the function bails before checking task plans.Fallback uses legacy non-DB renderer. At
workflow-projections.js:450:case "PLAN": renderPlanProjection(basePath, milestoneId, sliceId); // legacy, slice only return existsSync(filePath);renderPlanProjectionnever emitstasks/T##-PLAN.mdfiles and omits the## Files Likely Touchedsection — matching the two verifier messages:task plan missingandSlice plan missing 'Files Likely Touched' section.
A correct "TASK_PLAN" branch was added at lines 401-420 that calls renderTaskPlanFromDb per task, but the sole caller (auto-post-unit.js:929) only passes "PLAN", never "TASK_PLAN", so that branch is unreachable from the recovery path.
Net effect: post-unit verification fails → regenerateIfMissing("PLAN") returns false (early-return) or returns true with the legacy renderer that still doesn't write task plans → re-verify fails → dispatch repeats → stuck-stop after 3 iterations.
Expected Behavior
The "PLAN" branch should be authoritative for the slice's planning artifacts: use the DB-driven renderPlanFromDb for the slice file, AND iterate every task row to render any missing tasks/T##-PLAN.md via renderTaskPlanFromDb. Do not early-return on the slice file alone.
Suggested fix (applied locally):
if (fileType === "PLAN") {
let regenerated = 0;
if (!existsSync(filePath)) {
try {
await renderPlanFromDb(basePath, milestoneId, sliceId);
if (existsSync(filePath)) regenerated++;
} catch (err) { logWarning(...); }
}
const taskRows = getSliceTasks(milestoneId, sliceId);
for (const task of taskRows) {
const planPath = join(basePath, ".gsd", "milestones", milestoneId,
"slices", sliceId, "tasks", `${task.id}-PLAN.md`);
if (!existsSync(planPath)) {
try {
await renderTaskPlanFromDb(basePath, milestoneId, sliceId, task.id);
if (existsSync(planPath)) regenerated++;
} catch (err) { logWarning(...); }
}
}
return regenerated > 0;
}Environment
- GSD version: 3.0.0
- Model: claude-opus-4-7
- Unit:
plan-slice/M001/S04(also affected: S01, S02, S03)
Reproduction Context
Plan-slice phase. gsd_plan_slice succeeds (DB write OK, activity log: "Slice S04 planned."). Post-unit artifact verification then fails because tasks/T01-PLAN.md is missing on disk. Recovery via regenerateIfMissing(..., "PLAN") is a no-op. Auto-mode re-dispatches the same unit 3 times and stops with Stuck: plan-slice/M001/S04 derived 3 consecutive times without progress.
Forensic Evidence
Stuck-loop anomalies on every plan-slice unit in the session:
plan-slice/M001/S01dispatched 3×plan-slice/M001/S02dispatched 3×plan-slice/M001/S03dispatched 3×plan-slice/M001/S04dispatched 3× → terminal
Verifier warnings (verbatim):
verify-fail plan-slice M001/S04: task plan missing .gsd/milestones/M001/slices/S04/tasks/T01-PLAN.mdSlice plan missing 'Files Likely Touched' section
Journal pattern (S04, abridged):
[dispatch-match] rule="executing → execute-task (recover missing task plan → plan-slice)" unit=M001/S04
[unit-end] unit=M001/S04
[artifact-verification-retry] unit=M001/S04
[iteration-end]
... (repeats 3x) ...
[auto-exit] flow=64c318d0Related
- #6146
- #6343
Auto-generated by /gsd forensics
Source: gsd-build/gsd-2