Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/user-docs/auto-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ The sliding-window approach reduces false positives on legitimate retries (e.g.,

Auto mode also enforces a same-unit consecutive dispatch cap for `complete-milestone`, `validate-milestone`, and `research-slice`. The loop allows two consecutive dispatches of the same unit in the same phase and stops before a third attempt with a repeat-cap warning that instructs you to run `/gsd resume` after intervention.

Auto mode also tracks consecutive dispatch-claim skips with reason `already-active`. If the same unit claim returns `already-active` 3 times in a row, auto mode pauses and surfaces a manual-recovery warning instead of spinning indefinitely on claim retries.

### Artifact Verification Retries

After each unit, GSD verifies that the expected artifact exists on disk. If the artifact is missing, auto mode re-dispatches the unit with explicit failure context and records an `artifact-verification-retry` journal event.
Expand Down
8 changes: 8 additions & 0 deletions docs/user-docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ It checks:

**Fix:** Check the task plan for clarity. If the plan is ambiguous, refine it manually, then `/gsd auto` to resume.

### Auto mode pauses after repeated `already-active` dispatch claims

**Symptoms:** Auto mode repeatedly skips dispatch with reason `already-active`, then pauses with a message that manual recovery is required.

**Cause:** GSD treats 3 consecutive `already-active` claim skips for the same unit as a stuck claim path and pauses auto mode instead of retrying forever.

**Fix:** Resolve the underlying active-claim/worker state (usually with `/gsd doctor` or `/gsd doctor fix`), then run `/gsd auto` or `/gsd resume`.

### Auto mode pauses after a timeout or finalize failure

**Symptoms:** Auto mode reports a unit hard timeout, a finalize timeout, or a post-unit closeout failure.
Expand Down
2 changes: 2 additions & 0 deletions mintlify-docs/guides/auto-mode.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ A sliding-window analysis detects stuck loops — catching cycles like A→B→A

Auto mode also enforces a same-unit consecutive dispatch cap for `complete-milestone`, `validate-milestone`, and `research-slice`. The loop allows two consecutive dispatches of the same unit in the same phase and stops before a third attempt with a repeat-cap warning that instructs you to run `/gsd resume` after intervention.

Auto mode also tracks consecutive dispatch-claim skips with reason `already-active`. If the same unit claim returns `already-active` 3 times in a row, auto mode pauses and requires manual recovery instead of retrying claim skips indefinitely.

### Timeout supervision

| Timeout | Default | Behavior |
Expand Down
8 changes: 8 additions & 0 deletions mintlify-docs/guides/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ It checks file structure, referential integrity, completion state consistency, g
**Fix:** Check the task plan for clarity. Refine it manually, then `/gsd auto`.
</Accordion>

<Accordion title="Auto mode pauses after repeated already-active dispatch claims">
**Symptoms:** Auto mode repeatedly skips dispatch with reason `already-active`, then pauses for manual recovery.

**Cause:** GSD pauses when the same unit hits 3 consecutive `already-active` claim skips, preventing an infinite claim-retry loop.

**Fix:** Repair the active-claim/worker state (usually `/gsd doctor` or `/gsd doctor fix`), then continue with `/gsd auto` or `/gsd resume`.
</Accordion>

<Accordion title="Auto mode pauses after a timeout or finalize failure">
**Symptoms:** Auto mode reports a hard timeout, finalize timeout, or post-unit closeout failure.

Expand Down
16 changes: 16 additions & 0 deletions src/resources/extensions/gsd/auto/loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ import { handleCustomEngineReconcileOutcome } from "./workflow-custom-engine-rec
// tolerates — same behavior as a fresh session.
const STUCK_RECOVERY_ATTEMPTS_KEY = "stuck_recovery_attempts";
const RECENT_UNIT_KEYS_LIMIT = 20;
const MAX_CONSECUTIVE_ALREADY_ACTIVE_SKIPS = 3;

function stableStuckStateScopeId(s: AutoSession): string {
return normalizeRealPath(s.scope?.workspace.projectRoot ?? (s.originalBasePath || s.basePath));
Expand Down Expand Up @@ -292,6 +293,7 @@ export async function autoLoop(
};
let consecutiveErrors = 0;
let consecutiveCooldowns = 0;
let consecutiveAlreadyActiveSkips = 0;
const recentErrorMessages: string[] = [];

while (s.active) {
Expand Down Expand Up @@ -904,15 +906,29 @@ export async function autoLoop(
}
if (dispatchDecision.action === "skip") {
if (dispatchDecision.reason === "stale-lease") {
consecutiveAlreadyActiveSkips = 0;
const msg = `Lost milestone lease for ${iterData.mid ?? "unknown"} while claiming ${iterData.unitType} ${iterData.unitId}; dispatch claim still failed after recovery.`;
ctx.ui.notify(msg, "error");
finishTurn("stopped", "execution", msg);
await deps.stopAuto(ctx, pi, msg);
break;
}
if (dispatchDecision.reason === "already-active" || dispatchDecision.reason === "already_active") {
consecutiveAlreadyActiveSkips++;
if (consecutiveAlreadyActiveSkips >= MAX_CONSECUTIVE_ALREADY_ACTIVE_SKIPS) {
const msg = `Dispatch claim for ${iterData.unitType} ${iterData.unitId} remained already-active for ${consecutiveAlreadyActiveSkips} consecutive attempts. Pausing auto-mode for manual recovery.`;
ctx.ui.notify(msg, "error");
await deps.pauseAuto(ctx, pi);
finishTurn("paused", "manual-attention", msg);
break;
}
} else {
consecutiveAlreadyActiveSkips = 0;
}
finishTurn("skipped", "execution", dispatchDecision.reason);
continue;
}
consecutiveAlreadyActiveSkips = 0;
dispatchId = dispatchDecision.dispatchId;

let unitPhaseResult: Awaited<ReturnType<typeof runUnitPhaseViaContract>>;
Expand Down
6 changes: 5 additions & 1 deletion src/resources/extensions/gsd/tests/auto-loop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2025,7 +2025,7 @@ test("autoLoop handles dispatch skip action by continuing", async (t) => {
let dispatchCallCount = 0;
// Pre-queued dispatch responses: first call returns "skip", second returns "stop"
const dispatchResponses = [
{ action: "skip" as const },
{ action: "skip" as const, reason: "already-active" as const },
{ action: "stop" as const, reason: "done", level: "info" as const },
];
const deps = makeMockDeps({
Expand All @@ -2051,6 +2051,10 @@ test("autoLoop handles dispatch skip action by continuing", async (t) => {
deriveCalls.length >= 2,
"deriveState should be called at least twice (one per iteration)",
);
assert.ok(
!deps.callLog.includes("pauseAuto"),
"single already-active skip should not pause auto-mode",
);
});

test("autoLoop drains sidecar queue after postUnitPostVerification enqueues items", async (t) => {
Expand Down
Loading