Repository navigation
Description
Summary
The V1→V2 session importer (kv.migration.v1-v2) is a one-shot job. Sessions created by OpenCode 1.x after the first V2 launch remain in the legacy session / message / part tables and never appear in V2 session lists.
Environment
- opencode version: 2.0.7
- OS: Linux 7.1.8-200.fc44.x86_64 (linux x64) — Fedora 44
- Terminal: TERM=xterm-256color, COLORTERM=truecolor
- Shell: /bin/bash
- Install/channel: latest (
opencode v2.0.7) - Active plugins: none found in
~/.config/opencode/opencode.jsonc
Reproduction
- Install and use OpenCode 1.x long enough to have session history in
~/.local/share/opencode/opencode.db(session/message/part). - Launch OpenCode 2 (beta or 2.0.x) once. Confirm
GET /api/experimental/migration/v1returns{"status":"completed"}andkv.migration.v1-v2is{"phase":"completed"}. - Continue using OpenCode 1.x (for example 1.18.27 / 1.18.30) in the same data directory. Create new sessions.
- Switch back to OpenCode 2.0.x and open the global session list and the project/directory session list for those workspaces.
Expected Behavior
V2 should import any remaining V1 session rows that are not already present in session_v2, including sessions created after the first V2 launch. GET /api/experimental/migration/v1 should not report completed while unmigrated V1 sessions still exist.
Alternatively, mixed V1/V2 use after first V2 start should be documented as unsupported, with a supported way to re-run the importer.
Actual Behavior
The importer ran once on first V2 start and never ran again.
On this machine:
- First V2 launch:
0.0.0-beta-18999at 2026-09-04 01:21:50 UTC-4 kv.migration.v1-v2={"phase":"completed"}at that same timestamp- All 36 V1 sessions created before that timestamp exist in both
sessionandsession_v2 - All 7 V1 sessions created after that timestamp exist only in
session GET /api/session/{id}for those 7 IDs returnsSessionNotFoundError(HTTP 404)GET /api/experimental/migration/v1still returns{"status":"completed"}
Example missed session (still fully present in legacy tables):
- id:
ses_f82d98a72ffempGkC713ArwcVJ - title: Turing-cluster AI workload Terraform plan
- created 2026-09-07, updated 2026-09-10
- version: 1.18.27
- 318 messages / 1,492 parts
- also has 6,831 rows in
event(session.created.1,session.updated.1,message.updated.1,message.part.updated.1) - no
session_v2row, nosession_messagerows
V2 session lists (global and project/directory) therefore omit it.
Additional Context
- Related: V2: V1 session history is never imported into session_v2 — old sessions invisible in OpenCode 2 #41217 (closed as completed by feat(core): migrate v1 data to v2 #40723). This is a leftover gap: feat(core): migrate v1 data to v2 #40723 imported sessions that existed at first V2 start, but the completed KV marker prevents importing V1 sessions written afterwards.
- The published V1→V2 migration guide does not mention session history.
- Frequency: consistent. Every post-migration V1 session is missing; every pre-migration V1 session is present.
- Data is not deleted. Workaround today is reading the legacy SQLite tables directly. There is no user-facing command to re-run the importer.
- Counts from
~/.local/share/opencode/opencode.db(read-only):
session 43
session_v2 49
message 3032
part 13707
session_message 3240
before first V2 launch: 36 V1 sessions, all in session_v2
after first V2 launch: 7 V1 sessions, none in session_v2
The 7 missed sessions were written by OpenCode 1.18.27 and 1.18.30 while V2 already considered migration complete. Later V2-native sessions (2.0.3–2.0.7) exist only in session_v2, as expected.
Activity
github-actions commentedon Sep 18, 2026
This issue might be a duplicate of existing issues. Please check:
sessionandsession_v2#44028: Same underlying mechanism — mixed stable/beta channels sharing one data dir cause the one-shotkv.migration.v1-v2stamp to mark migration complete while V1 continues creating sessions that never reachsession_v2.drsmile1001 commentedon Sep 19, 2026
Three details not covered here or in #49412 / #44028. The first matters most, because the obvious workaround destroys data.
Resetting the KV marker destroys the event log
Deleting
kv.migration.v1-v2to force a re-run is not safe. From a cleared state the importer deletes the entireeventtable before it copies anything.Where V1 kept running after the marker was set, every row in
eventbelongs to the sessions that are still missing: V1 persists a durable event per message and part, while V2's CLI path persists none. Resetting the marker therefore wipes the event log of exactly the sessions being recovered.event_sequencemust not be lowered to the projection watermarkThe importer upserts
event_sequence.seqtotransformSession's watermark, which is only safe because it has already emptiedevent.For an aggregate whose events survive,
eventhas a unique index on(aggregate_id, seq)and new events publish atlatest + 1, so lowering the sequence makes the next publish collide with an existing row. Keeping the original value avoids this; the only cost is a gap insession_message.seq, harmless because the projector and the bus order by seq rather than assume contiguity. An incremental importer would need to handle this rather than reuse the current upsert.The status endpoint reports
completedwhile sessions are missingGET /api/experimental/migration/v1answers from the KV marker alone and never comparessessionagainstsession_v2, so anything built on it reports a healthy migration. Returningrequiredwhenever the difference is non-empty would be a cheap fix.Suggested direction
Drive the import from the set difference rather than a completion marker, drop the unconditional
eventwipe, and leaveevent_sequencealone for aggregates that still have events. That also makes the job re-entrant, which the mixed V1/V2 window requires.Workaround
transformSessionfrom@opencode/core/database/v1-migration.bunis exported as a pure function, so the missing sessions can be recovered out-of-band: copy the set difference intosession_v2and rebuildsession_messagefrommessageandpartthrough it, leavingevent,event_sequenceand the marker untouched. Run under bun, since the node condition resolves to a noop build. Snapshot with.backupand stop the service before writing.Environment
curlinstall scriptedyfox commentedon Sep 24, 2026
Confirmed independently on a different platform, with a working non-destructive
recovery path — neither of which I found in the existing reports.
Environment: Android / Termux + proot Debian,
opencode 2.0.15, single shared~/.local/share/opencode/opencode.db. Same failure: 78 V1 sessions created afteran earlier V2-beta trial were absent from the V2 view. All present in the legacy
sessiontable with fullmessage/parttranscripts.Root cause confirmed at the code level (bundled migration in the 2.0.15 binary):
the
eventtable is purged only when thekv.migration.v1-v2row is absent(
u === void 0). With the row present as{"phase":"sessions"}, the import re-runsand the purge is skipped. So a sticky
{"phase":"completed"}marker is preciselywhat strands V1 sessions written after it.
Safe recovery (verified, zero data loss):
eventpurge. Reset, neverdelete.
opencode serveon another port against the same DB, wait for the marker toreturn to
completed, then stop it.Result:
session156 →session_v2158 (156 V1 + 2 native V2), 0 missing,0 duplicate ids across
session_v2/session_message/part,eventintactat 65,781 rows.
Same root cause reported in #44028 and #49412.
Suggested fix direction: make the importer idempotent — re-run while
count(session) > count(session_v2 ∩ session)— and/or reportcompletedfromGET /api/experimental/migration/v1only when no unmigrated V1 rows remain. Themarker-reset procedure above would also make a good documented repair path.
neriousy commentedon Oct 20, 2026
Thanks for the detailed timeline and the warning about deleting the migration marker — this is the same problem as #50130, where we're tracking V1 sessions created after the first migration. Please subscribe or add a 👍 there, and feel free to add any further recovery details so they stay together.