From 4a7a96315d07e4f14154acd6131ce373019bbc2e Mon Sep 17 00:00:00 2001 From: EyJunge1 <149941075+EyJunge1@users.noreply.github.com> Date: Thu, 15 Oct 2026 23:46:12 +0200 Subject: [PATCH] fix(skills): reopen DB after boot-time Turso close SkillsManager kept a stale Turso handle when migrators called closeConnection on skills.db, breaking registerDbSkills. Detect stale handles via getConnection identity and reset on lifecycle close. Fixes #412 --- src/services/skills/skills-manager.ts | 10 ++++++++++ src/services/turso/lifecycle.ts | 2 ++ tests/skills-manager.test.ts | 25 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/src/services/skills/skills-manager.ts b/src/services/skills/skills-manager.ts index 214ae3a2..55b634f1 100644 --- a/src/services/skills/skills-manager.ts +++ b/src/services/skills/skills-manager.ts @@ -76,6 +76,16 @@ export class SkillsManager { private async getDb(): Promise { await this.initialize(); + // Connection manager is authoritative: boot migrators may close/replace + // skills.db underneath a cached TursoDb handle (encryption/engine rewrite). + if (this.db) { + const current = await tursoConnectionManager.getConnection(this.dbPath); + if (current === this.db) { + return this.db; + } + this.reset(); + await this.initialize(); + } if (!this.db) throw new Error("Skills database not initialized"); return this.db; } diff --git a/src/services/turso/lifecycle.ts b/src/services/turso/lifecycle.ts index 8e655308..5046db85 100644 --- a/src/services/turso/lifecycle.ts +++ b/src/services/turso/lifecycle.ts @@ -4,6 +4,7 @@ import { resetTursoReady } from "./ready.js"; import { userPromptManager } from "../user-prompt/user-prompt-manager.js"; import { userProfileManager } from "../user-profile/user-profile-manager.js"; import { aiSessionManager } from "../ai/session/ai-session-manager.js"; +import { skillsManager } from "../skills/skills-manager.js"; export async function closeTursoAndInvalidateCaches(): Promise { await tursoConnectionManager.closeAll(); @@ -12,4 +13,5 @@ export async function closeTursoAndInvalidateCaches(): Promise { userPromptManager.reset(); userProfileManager.reset(); aiSessionManager.reset(); + skillsManager.reset(); } diff --git a/tests/skills-manager.test.ts b/tests/skills-manager.test.ts index 952c7b49..7e01c19c 100644 --- a/tests/skills-manager.test.ts +++ b/tests/skills-manager.test.ts @@ -132,4 +132,29 @@ describe("SkillsManager", () => { stop(); expect(hits).toBe(3); }); + + it("reopens after closeTursoAndInvalidateCaches", async () => { + const mgr = await makeManager(); + await mgr.create({ id: "keep-me", name: "Keep", content: "body" }); + expect(await mgr.list({ enabledOnly: true })).toHaveLength(1); + + const { closeTursoAndInvalidateCaches } = await import("../src/services/turso/lifecycle.js"); + await closeTursoAndInvalidateCaches(); + + const listed = await mgr.list({ enabledOnly: true }); + expect(listed).toHaveLength(1); + expect(listed[0]?.id).toBe("keep-me"); + }); + + it("reopens after closeConnection on skills.db without lifecycle reset", async () => { + const mgr = await makeManager(); + await mgr.create({ id: "stale-guard", name: "Stale", content: "body" }); + expect(await mgr.list({ enabledOnly: true })).toHaveLength(1); + + await tursoConnectionManager.closeConnection(join(tmpDir, "skills.db")); + + const listed = await mgr.list({ enabledOnly: true }); + expect(listed).toHaveLength(1); + expect(listed[0]?.id).toBe("stale-guard"); + }); });