From 07489e582025cf1f9c623f028429e01c8dfe51ea Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 3 Jun 2026 20:42:00 -0700 Subject: [PATCH] fix(core): finish deferred agentLogEntries drop when migrations 103+ outrun it Migration 102 defers the destructive agentLogEntries drop until TaskStore copies legacy rows to JSONL and writes the __meta guard, then relies on a second init() pass gated on schemaVersion < SCHEMA_VERSION. Migrations 103-105 bump the version to 105 on the first pass, so the second pass never fired and the legacy table survived forever. Make the drop version-independent in migrate() and trigger the re-init whenever the legacy table remains. Also pin secrets-schema.test.ts to String(SCHEMA_VERSION) instead of the hardcoded "102" string the schema bump invalidated. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../core/src/__tests__/secrets-schema.test.ts | 8 +++---- packages/core/src/db.ts | 22 +++++++++++++++++++ packages/core/src/store.ts | 12 +++++++++- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/packages/core/src/__tests__/secrets-schema.test.ts b/packages/core/src/__tests__/secrets-schema.test.ts index 04ce264650..03c4a84866 100644 --- a/packages/core/src/__tests__/secrets-schema.test.ts +++ b/packages/core/src/__tests__/secrets-schema.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { Database } from "../db.js"; +import { Database, SCHEMA_VERSION } from "../db.js"; import { createCentralDatabase } from "../central-db.js"; function createTempDir(prefix: string): string { @@ -42,7 +42,7 @@ describe("secrets schema migrations", () => { const version = db .prepare("SELECT value FROM __meta WHERE key = 'schemaVersion'") .get() as { value: string }; - expect(version.value).toBe("102"); + expect(version.value).toBe(String(SCHEMA_VERSION)); } finally { db.close(); rmSync(dir, { recursive: true, force: true }); @@ -105,7 +105,7 @@ describe("secrets schema migrations", () => { const version = db .prepare("SELECT value FROM __meta WHERE key = 'schemaVersion'") .get() as { value: string }; - expect(version.value).toBe("102"); + expect(version.value).toBe(String(SCHEMA_VERSION)); } finally { db.close(); rmSync(dir, { recursive: true, force: true }); @@ -155,7 +155,7 @@ describe("secrets schema migrations", () => { .prepare("SELECT value FROM __meta WHERE key = 'schemaVersion'") .get() as { value: string }; - expect(projectVersion.value).toBe("102"); + expect(projectVersion.value).toBe(String(SCHEMA_VERSION)); expect(centralVersion.value).toBe("13"); } finally { projectDb.close(); diff --git a/packages/core/src/db.ts b/packages/core/src/db.ts index 2bf09735fd..5a0bd6ea02 100644 --- a/packages/core/src/db.ts +++ b/packages/core/src/db.ts @@ -2028,6 +2028,28 @@ export class Database { this.addColumnIfMissing("tasks", "scopeAutoWiden", "TEXT DEFAULT '[]'"); } + // Deferred agentLogEntries drop (companion to migration 102): when the + // legacy table still had rows on the first init pass, the destructive drop + // was deferred until TaskStore copies the rows to JSONL and writes the + // __meta guard, then re-runs init(). Migrations 103+ bump the schema + // version past 102 on that first pass, so the re-run can no longer reach + // the version-gated 102 block — finish the drop here, version-independent + // (and before the early return below, which fires once the version is + // current). + if (this.hasTable("agentLogEntries")) { + const agentLogMigrationComplete = this.getMetaValue("agentLogEntriesToFileMigrationVersion") === "1"; + const legacyAgentLogTableIsEmpty = + (this.db.prepare("SELECT COUNT(*) as count FROM agentLogEntries").get() as { count: number }).count === 0; + const hasLegacyAgentLogCitations = this.hasTable("goal_citations") + ? (this.db.prepare( + "SELECT 1 FROM goal_citations WHERE surface = 'agent_log' AND sourceRef GLOB 'agentLog:[0-9]*' LIMIT 1", + ).get() ?? undefined) !== undefined + : false; + if (agentLogMigrationComplete || (legacyAgentLogTableIsEmpty && !hasLegacyAgentLogCitations)) { + this.db.exec(`DROP TABLE IF EXISTS agentLogEntries`); + } + } + if (version >= SCHEMA_VERSION) return; if (version < 2) { diff --git a/packages/core/src/store.ts b/packages/core/src/store.ts index 16de1b0f07..a6b5a66df4 100644 --- a/packages/core/src/store.ts +++ b/packages/core/src/store.ts @@ -1443,7 +1443,17 @@ export class TaskStore extends EventEmitter { await this.migrateActiveArchivedTasksToArchiveDb(); await this.migrateAgentLogEntriesToFilesOnce(); await this.cleanupNoOpTaskMovedActivityRowsOnce(); - if (this.db.getSchemaVersion() < SCHEMA_VERSION) { + // Re-run init when migrations are pending, or when the deferred + // agentLogEntries drop still needs to fire: migration 102 skips the + // destructive drop until migrateAgentLogEntriesToFilesOnce() above writes + // the __meta guard, but migrations 103+ bump the schema version past 102 + // on the first pass, so the version check alone no longer triggers the + // second pass that performs the drop. + const legacyAgentLogTableRemains = + this.db + .prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'agentLogEntries' LIMIT 1") + .get() !== undefined; + if (this.db.getSchemaVersion() < SCHEMA_VERSION || legacyAgentLogTableRemains) { this.db.init(); } await this.importLegacyAgentLogsOnce();