From 22f623889f79e55a51d8c06c2ba9252c903cc752 Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Mon, 16 Mar 2026 22:27:39 -0600 Subject: [PATCH] fix: make skip-loop interruptible and count toward lifetime cap (#792) Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto.ts | 34 ++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index 8e95668b2..11732e27a 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -2426,18 +2426,31 @@ async function dispatchNextUnit( `Skip loop detected: ${unitType} ${unitId} skipped ${skipCount} times without advancing. Evicting completion record and forcing reconciliation.`, "warning", ); + if (!active) return; _skipDepth++; - await new Promise(r => setTimeout(r, 50)); + await new Promise(r => setTimeout(r, 150)); await dispatchNextUnit(ctx, pi); _skipDepth = Math.max(0, _skipDepth - 1); return; } + // Count toward lifetime cap so hard-stop fires during skip loops (#792) + const lifeSkip = (unitLifetimeDispatches.get(idempotencyKey) ?? 0) + 1; + unitLifetimeDispatches.set(idempotencyKey, lifeSkip); + if (lifeSkip > MAX_LIFETIME_DISPATCHES) { + await stopAuto(ctx, pi, `Hard loop: ${unitType} ${unitId} (skip cycle)`); + ctx.ui.notify( + `Hard loop detected: ${unitType} ${unitId} hit lifetime cap during skip cycle (${lifeSkip} iterations).`, + "error", + ); + return; + } ctx.ui.notify( `Skipping ${unitType} ${unitId} — already completed in a prior session. Advancing.`, "info", ); + if (!active) return; _skipDepth++; - await new Promise(r => setTimeout(r, 50)); + await new Promise(r => setTimeout(r, 150)); await dispatchNextUnit(ctx, pi); _skipDepth = Math.max(0, _skipDepth - 1); return; @@ -2473,18 +2486,31 @@ async function dispatchNextUnit( `Skip loop detected: ${unitType} ${unitId} skipped ${skipCount2} times without advancing. Evicting completion record and forcing reconciliation.`, "warning", ); + if (!active) return; _skipDepth++; - await new Promise(r => setTimeout(r, 50)); + await new Promise(r => setTimeout(r, 150)); await dispatchNextUnit(ctx, pi); _skipDepth = Math.max(0, _skipDepth - 1); return; } + // Count toward lifetime cap so hard-stop fires during skip loops (#792) + const lifeSkip2 = (unitLifetimeDispatches.get(idempotencyKey) ?? 0) + 1; + unitLifetimeDispatches.set(idempotencyKey, lifeSkip2); + if (lifeSkip2 > MAX_LIFETIME_DISPATCHES) { + await stopAuto(ctx, pi, `Hard loop: ${unitType} ${unitId} (skip cycle)`); + ctx.ui.notify( + `Hard loop detected: ${unitType} ${unitId} hit lifetime cap during skip cycle (${lifeSkip2} iterations).`, + "error", + ); + return; + } ctx.ui.notify( `Skipping ${unitType} ${unitId} — artifact exists but completion key was missing. Repaired and advancing.`, "info", ); + if (!active) return; _skipDepth++; - await new Promise(r => setTimeout(r, 50)); + await new Promise(r => setTimeout(r, 150)); await dispatchNextUnit(ctx, pi); _skipDepth = Math.max(0, _skipDepth - 1); return;