From 745865f1c6b667ff58fa434085a324b7657b15d5 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 18:49:50 -0700 Subject: [PATCH 1/2] fix(gsd): open DB on demand in gsd_milestone_status for non-auto sessions gsd_milestone_status checked isDbAvailable() but never called ensureDbOpen(), making it always fail outside auto-mode sessions where the DB is pre-opened during bootstrap. Replace with ensureDbOpen() which safely opens existing DB files without side effects when .gsd/ content exists. Closes #3644 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/bootstrap/query-tools.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/resources/extensions/gsd/bootstrap/query-tools.ts b/src/resources/extensions/gsd/bootstrap/query-tools.ts index 1dd53749c..30ecefecf 100644 --- a/src/resources/extensions/gsd/bootstrap/query-tools.ts +++ b/src/resources/extensions/gsd/bootstrap/query-tools.ts @@ -22,17 +22,18 @@ export function registerQueryTools(pi: ExtensionAPI): void { }), async execute(_toolCallId, params, _signal, _onUpdate, _ctx) { try { - // Strictly read-only: only use an already-open DB connection. - // Do NOT call ensureDbOpen() — it can create/migrate the DB as a side effect. + // Open the DB if not already open — safe for read-only use since + // ensureDbOpen() only creates/migrates when .gsd/ has content (#3644). + const { ensureDbOpen } = await import("./dynamic-tools.js"); + const dbAvailable = await ensureDbOpen(); const { - isDbAvailable, getMilestone, getSliceStatusSummary, getSliceTaskCounts, _getAdapter, } = await import("../gsd-db.js"); - if (!isDbAvailable()) { + if (!dbAvailable) { return { content: [{ type: "text" as const, text: "Error: GSD database is not available." }], details: { operation: "milestone_status", error: "db_unavailable" } as any, From a7af280c8136c4f15da8cb59ced2b37c941ec856 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 22:23:54 -0700 Subject: [PATCH 2/2] test: add regression test for query-tools ensureDbOpen usage Co-Authored-By: Claude Opus 4.6 (1M context) --- .../gsd/tests/query-tools-db-open.test.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/resources/extensions/gsd/tests/query-tools-db-open.test.ts diff --git a/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts b/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts new file mode 100644 index 000000000..6795cbe6e --- /dev/null +++ b/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts @@ -0,0 +1,47 @@ +/** + * Regression test for #3672 — query-tools uses ensureDbOpen + * + * gsd_milestone_status previously called isDbAvailable() but never + * ensureDbOpen(), making it always fail outside auto-mode sessions. + * The fix imports ensureDbOpen from dynamic-tools and calls it before + * querying the DB. + * + * This structural test verifies the ensureDbOpen import and usage exist + * in query-tools.ts. + */ + +import { describe, test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const source = readFileSync(join(__dirname, '..', 'bootstrap', 'query-tools.ts'), 'utf-8'); + +describe('query-tools ensureDbOpen usage (#3672)', () => { + test('imports ensureDbOpen from dynamic-tools', () => { + assert.match(source, /ensureDbOpen.*import\(|import.*ensureDbOpen/, + 'query-tools should import ensureDbOpen'); + }); + + test('calls ensureDbOpen() before DB queries', () => { + assert.match(source, /await ensureDbOpen\(\)/, + 'query-tools should call await ensureDbOpen()'); + }); + + test('no longer imports isDbAvailable in the execute path', () => { + // The old code imported isDbAvailable and checked it; the fix removed that + // The execute function should not destructure isDbAvailable from gsd-db + const executeBlock = source.slice(source.indexOf('async execute(')); + assert.doesNotMatch(executeBlock, /isDbAvailable,/, + 'execute path should not destructure isDbAvailable (replaced by ensureDbOpen)'); + }); + + test('uses dbAvailable result from ensureDbOpen', () => { + assert.match(source, /dbAvailable\s*=\s*await ensureDbOpen\(\)/, + 'should store ensureDbOpen result in dbAvailable'); + }); +});