bug: auto-mode recovery deadlocks after restart when task-plan projections are missing
Symptom
Auto-mode repeatedly stops with:
Stuck: plan-slice/M001/S04 derived 3 consecutive times without progressClosing and reopening the terminal does not recover it. A fresh post-restart session stops almost immediately, before any new plan-slice activity log is created.
This is a fresh [email protected] reproduction of the bug chain described in closed issues #6403, #6442, #6352, and #5465.
Environment
- Package:
[email protected] - Binary:
/home/muhammad/.npm-global/lib/node_modules/gsd-pi/dist/loader.js - Node:
v22.22.2 - npm:
10.9.7 - OS: Linux
- Isolation: worktree
- Project
.gsdis an externalized symlink:project/.gsd -> ~/.gsd/projects/<hash> - Project path contains spaces:
/home/muhammad/Documents/01-09 Apps/04 Tools/acx
Evidence
1. The original plan-slice write put task plans in the disposable worktree
The successful gsd_plan_slice tool result returned paths under the auto worktree:
.gsd/activity/033-plan-slice-M001-S04.jsonl:
planPath=/home/muhammad/.gsd/projects/646e11d85b75/worktrees/M001/.gsd/milestones/M001/slices/S04/S04-PLAN.md
taskPlanPaths=/home/muhammad/.gsd/projects/646e11d85b75/worktrees/M001/.gsd/milestones/M001/slices/S04/tasks/T01-PLAN.md,...The same pattern appears again in .gsd/activity/035-plan-slice-M001-S04.jsonl.
2. Verification looks in canonical project state, not the worktree
The verifier rejected the completed unit because the canonical task plan was missing:
.gsd/audit/events.jsonl:1229
verify-fail plan-slice M001/S04: task plan missing /home/muhammad/.gsd/projects/646e11d85b75/milestones/M001/slices/S04/tasks/T01-PLAN.md
.gsd/audit/events.jsonl:1230
journal-unit-end ... unitType=plan-slice unitId=M001/S04 status=completed artifactVerified=false
.gsd/audit/events.jsonl:1235
journal-artifact-verification-retry ... unitType=plan-slice unitId=M001/S04 attempt=23. After restart, recovery is blocked before the unit can run
Fresh post-restart evidence:
.gsd/audit/events.jsonl:1313
uok-kernel-enter ... sessionId=45031dbf-c7de-4239-be6a-667e54177b53
.gsd/audit/events.jsonl:1320
journal-dispatch-match ... rule="executing → execute-task (recover missing task plan → plan-slice)" unitType=plan-slice unitId=M001/S04
.gsd/audit/events.jsonl:1321
verify-fail plan-slice M001/S04: task plan missing /home/muhammad/.gsd/projects/646e11d85b75/milestones/M001/slices/S04/tasks/T01-PLAN.md
.gsd/audit/events.jsonl:1322
journal-auto-exit ... reason=other
.gsd/audit/events.jsonl:1323
terminal ... Stuck: plan-slice/M001/S04 derived 3 consecutive times without progress — 2 warning(s): parsers-legacy.ts require failed...No new activity log was created after .gsd/activity/035-plan-slice-M001-S04.jsonl, so the post-restart repair unit did not get a real attempt.
4. DB state proves this is recoverable, but runtime state blocks recovery
Read-only DB inspection:
runtime_kv:
scope=global
scope_id=/home/muhammad/Documents/01-09 Apps/04 Tools/acx
key=stuck_recovery_attempts
value_json=1
updated_at=2026-06-07T07:44:41.061Z
unit_dispatches:
plan-slice M001/S04 failed attempt 3 ended 2026-06-07T07:44:41.059Z error_summary=finalize-retry
plan-slice M001/S04 failed attempt 2 ended 2026-06-07T07:41:06.931Z error_summary=unit-break
plan-slice M001/S04 failed attempt 1 ended 2026-06-07T07:40:52.238Z error_summary=finalize-retry
tasks:
M001 S04 T01 pending Build resume resolver and launcher contracts
M001 S04 T02 pending Wire resume into CLI and built integration
M001 S04 T03 pending Document resume command and close regression proofOn disk, canonical .gsd/milestones/M001/slices/S04/S04-PLAN.md exists, but .gsd/milestones/M001/slices/S04/tasks/ is empty.
Root Cause
This appears to be a recovery deadlock across three code paths.
A. regenerateIfMissing(..., "PLAN") does not regenerate task plans
Installed workflow-projections.js still only writes the slice-level plan:
/home/muhammad/.gsd/agent/extensions/gsd/workflow-projections.js:75-85
renderPlanProjection(...) writes only `${sliceId}-PLAN.md`
/home/muhammad/.gsd/agent/extensions/gsd/workflow-projections.js:372-418
regenerateIfMissing(..., "PLAN") checks only S##-PLAN.md and returns false when that file existsSo once S04-PLAN.md exists but tasks/T##-PLAN.md files are missing, regeneration does nothing.
B. Stuck state persists into intentional restart
Installed auto/loop.js still restores stuck state unconditionally:
/home/muhammad/.gsd/agent/extensions/gsd/auto/loop.js:64-71
loadStuckState() rebuilds recentUnits from unit_dispatches and reads stuck_recovery_attempts from runtime_kv
/home/muhammad/.gsd/agent/extensions/gsd/auto/loop.js:183-188
autoLoop() initializes loopState from persisted recentUnits and stuckRecoveryAttemptsThen auto/phases.js detects stuck before dispatch:
/home/muhammad/.gsd/agent/extensions/gsd/auto/phases.js:999-1002
loopState.recentUnits.push({ key: derivedKey })
detectStuck(loopState.recentUnits)
/home/muhammad/.gsd/agent/extensions/gsd/auto/phases.js:1053-1093
stuckRecoveryAttempts > 0 goes to Level 2 hard stopThat matches the post-restart behavior: the repair dispatch is derived, but historical state causes an immediate Level-2 stop before execution.
C. parsers-legacy.ts fallback warnings pollute the stuck reason
The final terminal reason often hides the real verification failure behind benign parser fallback warnings:
parsers-legacy.ts require failed, falling back to .js: Cannot find module './parsers-legacy.ts'This is the same warning chain described in #6352. It is not the primary cause here, but it pollutes the stuck warning count and makes diagnosis harder.
Expected Behavior
Auto-mode should not require manual babysitting for a recoverable DB/projection mismatch.
At minimum:
- If DB has task rows and
S##-PLAN.mdexists buttasks/T##-PLAN.mdfiles are missing, recovery should regenerate the missing task plan projections. - An intentional fresh auto-mode start should give the derived repair unit at least one real attempt, instead of reusing prior
recentUnits/stuckRecoveryAttemptsto hard-stop immediately. - The
parsers-legacy.tsfallback should be debug/noise, not counted as a stuck-loop warning.
Proposed Fix
- Change
regenerateIfMissing(..., "PLAN")so it validates and regenerates both the slice plan and everytasks/T##-PLAN.mdfor DB task rows. Do not short-circuit only becauseS##-PLAN.mdexists. - On explicit user restart, reset or partition
recentUnitsandstuckRecoveryAttempts, unless the session is true crash recovery. This is the post-3.0 version of #5465. - Treat
recover missing task plan → plan-sliceas a recovery dispatch with a fresh attempt budget, or perform projection regeneration directly before stuck detection. - Suppress/downgrade the
parsers-legacy.tsfallback warning when.jsfallback succeeds.
Regression Test
Create a worktree-isolation fixture where:
.gsdis symlinked to~/.gsd/projects/<hash>.- DB contains a planned slice with task rows.
- Canonical
S##-PLAN.mdexists. - Canonical
tasks/T##-PLAN.mdfiles are missing. runtime_kv.stuck_recovery_attempts = 1andunit_dispatchescontains 3 prior failedplan-slice/<mid>/<sid>rows.- Start auto-mode intentionally, not crash recovery.
Expected: GSD regenerates task plans or allows one repair plan-slice attempt, then progresses to execute-task.
Actual: GSD immediately stops as stuck before a new activity log is written.
Related Closed Issues
- #6403 —
gsdProjectionRootworktree/project projection divergence - #6442 —
regenerateIfMissing(PLAN)does not regenerate task plan files - #6352 —
parsers-legacy.tswarning triggers/pollutes stuck detector - #5465 — stuck recovery state persists into intentional restart
Those were closed as stale or because the repo moved, but this remains reproducible on installed [email protected].
Source: gsd-build/gsd-2