#6470·gsd-2

bug: auto-mode recovery deadlocks after restart when task-plan projections are missing

Author: DoozyDozCreated Jun 7, 2026Updated Jun 22, 2026
Labelsneeds-triage

Symptom

Auto-mode repeatedly stops with:

Stuck: plan-slice/M001/S04 derived 3 consecutive times without progress

Closing 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 .gsd is 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=2

3. 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 proof

On 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 exists

So 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 stuckRecoveryAttempts

Then 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 stop

That 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:

  1. If DB has task rows and S##-PLAN.md exists but tasks/T##-PLAN.md files are missing, recovery should regenerate the missing task plan projections.
  2. An intentional fresh auto-mode start should give the derived repair unit at least one real attempt, instead of reusing prior recentUnits/stuckRecoveryAttempts to hard-stop immediately.
  3. The parsers-legacy.ts fallback should be debug/noise, not counted as a stuck-loop warning.

Proposed Fix

  1. Change regenerateIfMissing(..., "PLAN") so it validates and regenerates both the slice plan and every tasks/T##-PLAN.md for DB task rows. Do not short-circuit only because S##-PLAN.md exists.
  2. On explicit user restart, reset or partition recentUnits and stuckRecoveryAttempts, unless the session is true crash recovery. This is the post-3.0 version of #5465.
  3. Treat recover missing task plan → plan-slice as a recovery dispatch with a fresh attempt budget, or perform projection regeneration directly before stuck detection.
  4. Suppress/downgrade the parsers-legacy.ts fallback warning when .js fallback succeeds.

Regression Test

Create a worktree-isolation fixture where:

  1. .gsd is symlinked to ~/.gsd/projects/<hash>.
  2. DB contains a planned slice with task rows.
  3. Canonical S##-PLAN.md exists.
  4. Canonical tasks/T##-PLAN.md files are missing.
  5. runtime_kv.stuck_recovery_attempts = 1 and unit_dispatches contains 3 prior failed plan-slice/<mid>/<sid> rows.
  6. 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 — gsdProjectionRoot worktree/project projection divergence
  • #6442 — regenerateIfMissing(PLAN) does not regenerate task plan files
  • #6352 — parsers-legacy.ts warning 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].