From 479ffd127c00225dabc4c3d991435edc6c34c11c Mon Sep 17 00:00:00 2001 From: drkthng Date: Mon, 13 Apr 2026 14:10:57 +0200 Subject: [PATCH] fix(state): prevent false degraded-mode warning when DB not yet initialized (#3922) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(state): prevent false degraded-mode warning when DB not yet initialized deriveState() is called during before_agent_start context injection, before any tool invocation has had a chance to open the DB. Previously, isDbAvailable() returning false in this path triggered a misleading "DB unavailable — using filesystem state derivation (degraded mode)" warning, even though the DB was simply not yet initialized (not failed). Add a _dbOpenAttempted flag in gsd-db.ts that tracks whether openDatabase() has been called at least once. The degraded-mode warning now only fires when the DB was actually attempted and failed to open, not when it hasn't been initialized yet. Co-Authored-By: Claude Opus 4.6 * test(state): add regression test for false degraded-mode warning Adds the test file that was missing from the previous commit, fixing the CI require-tests gate. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .../tests/false-degraded-mode-warning.test.ts | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts diff --git a/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts b/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts new file mode 100644 index 000000000..510a88e9a --- /dev/null +++ b/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts @@ -0,0 +1,104 @@ +/** + * false-degraded-mode-warning.test.ts — Regression tests for #3922. + * + * Before this fix, deriveState() logged a "DB unavailable — degraded mode" + * warning even when the DB simply hadn't been opened yet (e.g. during + * before_agent_start context injection). The fix introduces wasDbOpenAttempted() + * to distinguish "not yet initialized" from "genuinely unavailable." + * + * Two aspects: + * 1. gsd-db: wasDbOpenAttempted() tracks whether openDatabase() was ever called. + * 2. state: the degraded-mode warning is gated behind wasDbOpenAttempted(). + */ + +import { describe, test } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { + openDatabase, + closeDatabase, + isDbAvailable, + wasDbOpenAttempted, +} from "../gsd-db.ts"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const stateSource = readFileSync(join(__dirname, "..", "state.ts"), "utf-8"); + +// ═══════════════════════════════════════════════════════════════════════════ +// 1. gsd-db: wasDbOpenAttempted flag +// ═══════════════════════════════════════════════════════════════════════════ + +describe("wasDbOpenAttempted (#3922)", () => { + + test("wasDbOpenAttempted returns true after openDatabase is called", () => { + // By this point in the test suite, openDatabase may or may not have been + // called by other tests. So we call it explicitly and verify it returns true. + openDatabase(":memory:"); + assert.strictEqual(wasDbOpenAttempted(), true, + "wasDbOpenAttempted should be true after openDatabase call"); + closeDatabase(); + }); + + test("openDatabase sets the flag even if it fails on invalid path", () => { + // openDatabase with an unreachable path may fail, but the flag should + // still be set because the attempt was made. + try { openDatabase("/nonexistent/path/that/will/fail.db"); } catch { /* expected */ } + assert.strictEqual(wasDbOpenAttempted(), true, + "wasDbOpenAttempted should be true even after a failed open attempt"); + }); +}); + +// ═══════════════════════════════════════════════════════════════════════════ +// 2. state.ts: degraded-mode warning is gated behind wasDbOpenAttempted +// ═══════════════════════════════════════════════════════════════════════════ + +describe("degraded-mode warning guard (#3922)", () => { + + test("state.ts imports wasDbOpenAttempted from gsd-db", () => { + assert.ok( + stateSource.includes("wasDbOpenAttempted"), + "state.ts must import wasDbOpenAttempted to gate the degraded-mode warning", + ); + }); + + test("degraded-mode warning is inside a wasDbOpenAttempted() guard", () => { + // Find the degraded-mode warning string + const warningStr = 'DB unavailable — using filesystem state derivation (degraded mode)'; + const warningIdx = stateSource.indexOf(warningStr); + assert.ok(warningIdx > 0, "degraded-mode warning string must exist in state.ts"); + + // The wasDbOpenAttempted() check must appear BEFORE the warning, + // within the same else-branch (i.e. within a reasonable distance). + // Look backwards from the warning for the guard. + const searchWindow = stateSource.slice(Math.max(0, warningIdx - 300), warningIdx); + assert.ok( + searchWindow.includes("wasDbOpenAttempted()"), + "wasDbOpenAttempted() guard must appear shortly before the degraded-mode warning " + + "to prevent false warnings when DB has not been initialized yet", + ); + }); + + test("warning is NOT emitted unconditionally in the else branch", () => { + // The old code had `logWarning(...)` directly in the else branch. + // The fix wraps it in `if (wasDbOpenAttempted())`. + // Verify the logWarning call is inside a conditional, not bare. + const lines = stateSource.split("\n"); + for (let i = 0; i < lines.length; i++) { + if (lines[i]!.includes("DB unavailable") && lines[i]!.includes("degraded mode")) { + // This line has the warning. Check that the preceding non-empty line + // contains an if-condition (wasDbOpenAttempted), not a bare else. + let prev = i - 1; + while (prev >= 0 && lines[prev]!.trim() === "") prev--; + const prevLine = lines[prev]!.trim(); + assert.ok( + prevLine.includes("wasDbOpenAttempted"), + `Line ${i + 1} emits degraded-mode warning — preceding line ${prev + 1} must ` + + `contain wasDbOpenAttempted guard, but found: "${prevLine}"`, + ); + break; + } + } + }); +});