fix(core): skip orphaned beta sessions

This commit is contained in:
Dax Raad
2026-08-05 18:12:54 -04:00
parent 01dd79f984
commit 09417d5d51
3 changed files with 19 additions and 10 deletions
+2 -1
View File
@@ -269,7 +269,8 @@ Before transforming V1 rows, look for `opencode-next.db` in the data directory.
builds. Open it read-only with Bun SQLite and copy its `project`, `session`, and `session_message` rows directly into the
current `project`, `session_v2`, and `session_message` tables. Existing current projects and Sessions win ID collisions.
Do not copy its durable events or runtime caches; initialize each imported Session's `event_sequence` watermark from its
maximum message sequence. Commit each imported Session independently and leave the source database untouched.
maximum message sequence. Commit each imported Session independently and leave the source database untouched. Skip and
warn for beta Sessions whose referenced project row is missing rather than blocking the remaining migration.
The previous V2 import is part of this migration and uses the same completion marker. It needs no source-specific cursor:
the destination Session row is the per-Session idempotency boundary, so a retry skips transactions that already committed.
+9 -4
View File
@@ -618,10 +618,15 @@ function importNextDatabase(
nextCompleted = 0
for (const session of sessions) {
const project = projects.get(session.project_id)
if (!project)
return yield* Effect.die(
new Error(`Previous V2 session ${session.id} references missing project ${session.project_id}`),
)
if (!project) {
yield* Effect.logWarning("Skipped previous V2 session with missing project", {
sessionID: session.id,
projectID: session.project_id,
})
nextCompleted++
yield* Effect.yieldNow
continue
}
const messages = source
.query<NextMessage, [string]>(
"SELECT id, session_id, type, seq, time_created, time_updated, data FROM session_message WHERE session_id = ? ORDER BY seq",
+8 -5
View File
@@ -825,10 +825,12 @@ describe("V1Migration database workflow", () => {
) VALUES
('ses_next', 'next-project', 'next', 'C:/Users/sewer', 'Imported', '2', 'build',
'{"id":"model","providerID":"provider"}', 10, 20),
('ses_existing', 'next-project', 'source-existing', '/tmp/next', 'Source existing', '2', NULL, NULL, 11, 21);
('ses_existing', 'next-project', 'source-existing', '/tmp/next', 'Source existing', '2', NULL, NULL, 11, 21),
('ses_orphan', 'missing-project', 'orphan', '/tmp/orphan', 'Orphan', '2', NULL, NULL, 12, 22);
INSERT INTO session_message VALUES
('msg_next', 'ses_next', 'user', 4, 12, 13, '{"text":"from next","time":{"created":12}}'),
('msg_source_existing', 'ses_existing', 'user', 2, 12, 13, '{"text":"source","time":{"created":12}}');
('msg_source_existing', 'ses_existing', 'user', 2, 12, 13, '{"text":"source","time":{"created":12}}'),
('msg_orphan', 'ses_orphan', 'user', 0, 12, 13, '{"text":"orphan","time":{"created":12}}');
`)
source.close()
@@ -851,13 +853,13 @@ describe("V1Migration database workflow", () => {
expect(yield* V1Migration.status({ nextDatabasePath: filename })).toEqual({
status: "required",
completed: 0,
total: 2,
total: 3,
})
expect(yield* V1Migration.run({ nextDatabasePath: filename })).toEqual({ status: "completed" })
expect(yield* V1Migration.status({ nextDatabasePath: filename })).toEqual({
status: "completed",
completed: 2,
total: 2,
completed: 3,
total: 3,
})
expect(yield* db.get(sql`SELECT title, agent, model FROM session_v2 WHERE id = 'ses_next'`)).toEqual({
title: "Imported",
@@ -888,6 +890,7 @@ describe("V1Migration database workflow", () => {
expect(yield* db.all(sql`SELECT id FROM session_message WHERE session_id = 'ses_existing'`)).toEqual([
{ id: "msg_current_existing" },
])
expect(yield* db.get(sql`SELECT id FROM session_v2 WHERE id = 'ses_orphan'`)).toBeUndefined()
expect(yield* db.get(sql`SELECT name, worktree FROM project WHERE id = 'next-project'`)).toEqual({
name: "Current project",
worktree: "/tmp/current",