Skip to content

fix(core): preserve 1.19 API messages during migration - #58549

Open
r266-tech wants to merge 2 commits into
anomalyco:devfrom
r266-tech:preserve-next-messages
Open

r266-tech wants to merge 2 commits into
anomalyco:devfrom
r266-tech:preserve-next-messages

Conversation

@r266-tech

@r266-tech r266-tech commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #58543

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Preserve messages created through 1.19's /api/session when upgrading to V2. There are two deletion points: the August table split drops the existing projection, and the V1 importer later rebuilds it from empty legacy tables.

Stage the native messages durably during the split, then let the existing importer resolve project ownership and relative directories. Merge legacy history before native history, preferring native rows for shared IDs. Native-only message sequences, JSON content, session metadata and usage are preserved. The staging table and completion marker commit together, so interrupted imports can resume; already imported opencode-next.db sessions remain authoritative.

This prevents loss during migration; it cannot reconstruct messages already deleted from an upgraded database without the original data. Late V1 writes after migration (#57080) remain separate.

How did you verify your code works?

  • Real SQLite regression with foreign keys enabled, using the migration sequence shipped in v1.19.3, native-only, legacy-only, mixed history, shared message IDs, project attribution and relative directories. It also interrupts a message insert, verifies rollback and the durable cursor, then resumes and verifies idempotence and staging cleanup. The original regression fails on dev; mixed-history coverage also fails on the initial PR revision.
  • Both migration test files: 56 passed, including 10 randomized runs while the full core suite ran (560 passes).
  • Full core: 6677 passed, 49 skipped, 74 failed. Unmodified dev: 6676 passed, 49 skipped, the exact same 74 failures (local Git transport guard and shell/config fixtures); no new failing cases.
  • Repository bun run check passed (lint and 34 workspace typechecks); formatting and diff checks passed. Bun 1.4.4, macOS. No live provider or user database used.

AI-assisted implementation and verification.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #57000, #58041, #50130

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix works: I reproduced #58543 with real [email protected] and the issue's script. On the base, the V1 import emptied the session's session_message rows (4 → 0, the messages endpoint returned []). With this PR all 4 rows survive and the messages endpoint returns them. The new test fails on the base and passes here, and test/v1-migration.test.ts passes in full (37/37). I couldn't run the type check because my local Bun lacks bun check.

The public API is unchanged.

Two migration edge cases are worth fixing (details inline):

  • Sessions copied during the schema step skip the importer's project attribution and relative-directory fix. In my run the session moved from its resolved project to global.
  • A session with both legacy message/part rows and native session_message rows now has its legacy messages dropped instead of its native ones.

The fix lives in an already shipped migration. That works for 1.19 databases that haven't upgraded yet, which is the case this targets. As the description notes, databases that already ran it can't be recovered.

Related: #57000, #58041, #50130

`)
// 1.19's /api/session already writes V2 messages in this database.
// Preserve their sessions before moving the projection's foreign key.
yield* tx.run(`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This copies session rows straight into session_v2, so these sessions skip the importer's handling: reassigning missing projects, resolving global sessions to their directory's project (#56322) and fixing relative directories (#57618). The importer then sees them as imported and never revisits them.

In the issue's reproduction, the base ends up with the session under its resolved project (92de9e…, with a project row). With this PR it stays project_id = 'global' and no project row is created. Could the schema step keep only the session_message rows (for example in a temporary table), and leave creating session_v2 rows to the importer?

})
.run()
// Native V2 history (including 1.19 API sessions) is already authoritative.
if (nextID.imported) return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imported is now true for every session the schema step copied, i.e. any session with native session_message rows, so the legacy message/part import is skipped for all of them. A session with rows in both stores (for example a legacy session later continued through /api/session in 1.19) would lose its legacy history. On the base it lost the native messages instead.

Skipping only when the session has no legacy message/part rows would keep the current behaviour for mixed sessions. Merging both would be better still.

@r266-tech

Copy link
Copy Markdown
Contributor Author

Addressed both findings in f2898ff. The schema step now stages only native messages in a durable table; session creation still goes through the importer’s ownership and relative-directory handling. The importer merges legacy history before native history (native wins for shared IDs), then drops staging atomically with completion. Added real SQLite coverage for attribution, relative paths, mixed history, duplicate IDs and an interrupted insert/resume. Both migration suites pass, including 560 randomized repetitions; the full core run has the same 74 local-environment failures as unmodified dev, with no new failures.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both earlier points are addressed. Native 1.19 messages are now staged during the schema step and merged by the importer, so project attribution and directory handling still apply, and mixed sessions keep both legacy and native history.

I re-ran the #58543 reproduction from source on the same 1.19.3 database. All 4 session_message rows survive and the messages endpoint returns them. The session gets its resolved directory project, as on the base, and the staging table is dropped when the import finishes. test/v1-migration.test.ts passes (37/37). I couldn't run the type check because my local Bun lacks bun check.

The public API is unchanged.

No new concerns.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

migration: sessions created via 1.19 /api/session lose all messages on v1->v2 migration (2.1.3)

1 participant