fix: gate auto-mode bootstrap on SQLite availability (#2419) (#2421)

This commit is contained in:
Tom Boucher 2026-03-24 23:37:19 -04:00 committed by GitHub
parent 6409070225
commit 17e172b466
2 changed files with 77 additions and 0 deletions

View file

@ -551,6 +551,20 @@ export async function bootstrapAutoSession(
}
}
// Gate: abort bootstrap if the DB file exists but the provider is
// still unavailable after both open attempts above. Without this,
// auto-mode starts but every gsd_task_complete / gsd_slice_complete
// call returns "db_unavailable", triggering artifact-retry which
// re-dispatches the same task — producing an infinite loop (#2419).
if (existsSync(gsdDbPath) && !isDbAvailable()) {
ctx.ui.notify(
"SQLite database exists but failed to open. Auto-mode cannot proceed without a working database provider. " +
"Check for corrupt gsd.db or missing native SQLite bindings.",
"error",
);
return releaseLockAndReturn();
}
// Initialize metrics
initMetrics(s.basePath);

View file

@ -0,0 +1,63 @@
/**
* sqlite-unavailable-gate.test.ts #2419
*
* When the SQLite provider fails to open, bootstrapAutoSession must
* refuse to start auto-mode. Otherwise gsd_task_complete returns
* "db_unavailable", artifact retry re-dispatches the same task, and
* the session loops forever.
*
* This test verifies the gate by reading auto-start.ts source and
* confirming the pattern: after the DB lifecycle block, if the DB
* file exists on disk but isDbAvailable() still returns false after
* the open attempt, bootstrap must abort with an error notification.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { createTestContext } from "./test-helpers.ts";
const { assertTrue, report } = createTestContext();
const srcPath = join(import.meta.dirname, "..", "auto-start.ts");
const src = readFileSync(srcPath, "utf-8");
console.log("\n=== #2419: SQLite unavailable gate in auto-start.ts ===");
// The DB lifecycle section tries to open the DB. After those try/catch
// blocks, there must be a HARD GATE: if the DB file exists on disk but
// isDbAvailable() is still false (open failed), bootstrap must abort
// by calling releaseLockAndReturn() with an error notification.
const dbLifecycleIdx = src.indexOf("DB lifecycle");
assertTrue(dbLifecycleIdx > 0, "auto-start.ts has a DB lifecycle section");
const afterDbLifecycle = src.slice(dbLifecycleIdx);
// Find the second isDbAvailable check — the one AFTER the open attempts.
// The first check at line ~543 tries to open the DB.
// There must be a SECOND check that gates bootstrap if it's still unavailable.
const firstCheck = afterDbLifecycle.indexOf("isDbAvailable()");
assertTrue(firstCheck > 0, "DB lifecycle section has isDbAvailable() check");
const afterFirstCheck = afterDbLifecycle.slice(firstCheck + "isDbAvailable()".length);
const secondCheck = afterFirstCheck.indexOf("isDbAvailable()");
assertTrue(
secondCheck > 0,
"auto-start.ts has a SECOND isDbAvailable() check after the open attempt — this is the gate (#2419)",
);
// The second check must lead to releaseLockAndReturn (abort bootstrap)
if (secondCheck > 0) {
const gateRegion = afterFirstCheck.slice(secondCheck, secondCheck + 500);
assertTrue(
gateRegion.includes("releaseLockAndReturn"),
"The DB availability gate calls releaseLockAndReturn() to abort bootstrap (#2419)",
);
assertTrue(
/database|sqlite|db.*unavailable/i.test(gateRegion),
"The DB availability gate includes a user-facing error message about the database (#2419)",
);
}
report();