Repository navigation
Description
core: previous V2 import is skipped when a fresh target has no legacy session table
Summary
When an existing, importable opencode-next.db is beside a newly bootstrapped current database, previous-V2 import is skipped if the target lacks the legacy V1 session table. Both migration status and execution report completed, although no source sessions or messages were imported. The same source imports successfully when empty legacy tables are present.
Environment
- Bun 1.4.2 (
744846f84); Windows 10.0.22631, x64. - Executed custom source:
72d0dce8ea437ea061e0680110fa19a81aa118fd. - Official comparison:
63c1a27af5a391d9caa55b82694f496911e1859b; the affected importer implementation is identical. - Follow-up comparison against official dev
60a5a6cfound only three AI-file changes relative to63c1a27; the relevant source remains unchanged. That revision was not separately executed. - Fully private synthetic source/target databases, real SQLite driver and target bootstrap; no user database, service, credentials, provider, or external HTTP request was accessed. The custom schema's additional
start_directorycolumn does not change the missing-legacy-table condition.
Reproduction
Line references below use the executed custom commit.
- In a new private data directory, create
opencode-next.dbusing the existing source fixture frompackages/core/test/v1-migration.test.ts:934-970(imports previous V2 sessions and messages containing apostrophes). It has the historicalproject,session, andsession_messagetables, with three source sessions and three messages. Close the source connection. - Build a separate, initially nonexistent
opencode.dbusing the actualDatabase.layer({ path: targetPath })frompackages/core/src/database/database.ts:67-79. ProvideGlobal.Servicewith that private directory asdataand other paths isolated. This runs the real current schema bootstrap. Confirm that the target containssession_v2but no legacysessiontable. - Do not call
createLegacyTablesin this case. The existing shared test helper atpackages/core/test/v1-migration.test.ts:880-887calls it unconditionally at line 885, masking the fresh-target condition. Reuse the source fixture, but omit this one legacy-table setup call for the failing case. - With the bootstrapped target's
Database.Service, call the actualV1Migration.status(),V1Migration.run(), thenV1Migration.status(). LeaveOPENCODE_DBunset, not:memory:, sorun()discovers the privateopencode-next.dbthrough its normal path. Query targetsession_v2,session_message, and thekvrow keyed bymigration.v1-v2. - Control: repeat with a second fresh target and the identical source fixture, but call the existing
createLegacyTableshelper atpackages/core/test/v1-migration.test.ts:825-878after bootstrap. No legacy rows need to be inserted. Compare source-file hashes before/after both cases.
Expected Behavior
The supported previous-V2 source is imported regardless of whether the destination has V1 tables. Status must not report completion while that applicable import is skipped; successful migration should record its completion marker using the current kv protocol.
Actual Behavior
| Observation | Empty legacy tables present | No legacy tables |
|---|---|---|
| Status before execution | required |
completed |
| Execution result / final status | completed / completed |
completed / completed |
| Imported sessions / messages | 3 / 3 | 0 / 0 |
kv["migration.v1-v2"] |
{"phase":"completed"} |
No row |
| Source file unchanged | Yes | Yes |
The failing regression expected three imported sessions and received an empty array. The source remains intact: this is skipped import and false completion, not evidence of permanent data loss or of every upgrade being affected.
Additional Context
packages/core/src/database/v1-migration.bun.ts:493-501,534-541returnscompletedwhen the destination lacks legacysession. That returns before previous-V2 discovery/import at 567-579. The legacy-table check is at 1130-1134; the default previous-V2 source path is at 702-705.- Real fresh-database bootstrap is at
packages/core/src/database/migration.ts:22-46; it installs the current schema rather than a V1sessiontable. - The historical source shape is explicitly supported: official ancestor
0fbe489e357c5463bf2f5ef1b6cfcb619bab88cf,docs/design/v1-v2-database-migration.md:268-277, specifies importing pre-launchopencode-next.dband its three tables. Lines 282-284 discuss fresh-database migration. This is historical intent evidence, not a claim that its old endpoint/key details remain current. Current positive import tests also retain this source fixture. - The affected condition is an existing previous-V2 source plus a fresh destination without V1 tables. The prevalence of that installation/recovery condition was not measured. An initial duplicate search found no exact match; it was not exhaustive.
Suggested solution (not implemented)
Decouple previous-V2 source discovery/import from detection of V1 tables. Preserve the existing per-session transactions and collision/idempotency behavior; run V1 backfill only when its tables exist. Derive accurate completion status from all applicable work and write the current completion marker when that work succeeds. Add the no-legacy fresh-target case beside the existing legacy-table control; no migration-framework rewrite is needed.
Activity
opencode-agent commentedon Oct 18, 2026
Thanks for the detailed report. I can confirm this on the latest
dev(60a5a6c), and v2.0.32 has the same code.To reproduce it, I created an
opencode-next.dbwith the historicalproject/session/session_messagetables and bootstrapped a fresh target with the current schema only. I then ranV1Migration.status(),run()andstatus()twice: once with empty legacy V1 tables in the target and once without them.requiredand the source session is imported.run()both reportcompleted,session_v2andsession_messagestay empty, and nomigration.v1-v2row is written tokv.The cause looks like the early
hasLegacySessionsreturn in bothstatus()andrun()inpackages/core/src/database/v1-migration.bun.ts. It happens before the previous-V2 import runs. The source database isn't modified. The test script below reproduces it.Reproduction script (fresh-target-import.test.ts)
Run it:
bun fresh-target-import.test.ts