From c9f83fce95c1166b96508b60608accf4b452df07 Mon Sep 17 00:00:00 2001 From: Divit Kashyap <162712154+divitkashyap@users.noreply.github.com> Date: Sun, 24 May 2026 15:25:54 +0100 Subject: [PATCH] fix(session): preserve time_updated on project_id migration Bulk-rewriting project_id on session rows during startup project discovery (Project.fromDirectory and Project.migrateProjectId) was firing Drizzle's $onUpdate hook on time_updated, scrambling the /sessions timeline by stamping every migrated session with the startup timestamp. Pass the column through unchanged via sql\`\${SessionTable.time_updated}\` so Drizzle skips the auto-update, matching the existing pattern in projectors.ts and data-migration.ts. Closes #25392 --- packages/opencode/src/project/project.ts | 2 +- packages/opencode/test/project/migrate-global.test.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 2ef299e94452..5e968f8552bb 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -315,7 +315,7 @@ export const layer = Layer.effect( yield* db((d) => d .update(SessionTable) - .set({ project_id: projectID }) + .set({ project_id: projectID, time_updated: sql`${SessionTable.time_updated}` }) .where(and(eq(SessionTable.project_id, ProjectID.global), eq(SessionTable.directory, data.directory))) .run(), ) diff --git a/packages/opencode/test/project/migrate-global.test.ts b/packages/opencode/test/project/migrate-global.test.ts index 6efd670c5c98..504849da48ae 100644 --- a/packages/opencode/test/project/migrate-global.test.ts +++ b/packages/opencode/test/project/migrate-global.test.ts @@ -104,6 +104,14 @@ describe("migrateFromGlobal", () => { const id = legacySessionID() yield* Effect.sync(() => seed({ id, dir: tmp, project: ProjectID.global })) + // Capture the original time_updated so we can verify migration + // does not corrupt it. Regression test for #25392 — without the + // preserve clause, Drizzle's $onUpdate hook bumps time_updated to + // Date.now() on every startup, scrambling the /sessions timeline. + const before = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) + expect(before).toBeDefined() + const originalTimeUpdated = before!.time_updated + // 4. Call fromDirectory again — project row already exists, // so the current code skips migration entirely. This is the bug. yield* projects.fromDirectory(tmp) @@ -111,6 +119,8 @@ describe("migrateFromGlobal", () => { const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) expect(row).toBeDefined() expect(row!.project_id).toBe(project.id) + // time_updated must be preserved across the project_id rewrite. + expect(row!.time_updated).toBe(originalTimeUpdated) }), )