Repository navigation
Description
Description
OpenCode 2 performs a one-time V1 to V2 session import whose state lives in kv['migration.v1-v2']. Once that key is {"phase":"completed"}, any V1 session that appears in the same database afterwards is never imported: it stays in the legacy session / message / part tables, is absent from session_v2, GET /api/session/{id} returns 404, and desktop tabs that reference it show "the session this tab points to no longer exists on this server".
I hit this because I kept using a V1 client (1.18.31) alongside the V2 desktop app. The migration completed on 2026-09-17 (first V2 startup, 2.0.3); 11 sessions created with V1 after that were invisible in V2. The same first run also skipped 29 V1 sessions that already existed in the database. The data was never lost, but nothing surfaced the problem.
Plugins
oh-my-openagent@latest
OpenCode version
2.0.10 (desktop bundled CLI; migration.v1-v2 state was created by 2.0.3, 2.0.5 and 2.0.8 were also installed in between)
Steps to reproduce
- Create sessions with a V1 client (1.18.x) in
~/.local/share/opencode/opencode.db. - Let a V2 build finish a startup so
kv['migration.v1-v2']becomes{"phase":"completed"}(here: 2.0.3, even though 29 V1 sessions were already present). - Create new sessions with the V1 client against the same database.
- Open the V2 desktop app: the new sessions are missing.
opencode api get /api/session/<id>returns 404,opencode api get /api/experimental/migration/v1keeps returning{"status":"completed"}.
Expected behavior
The one-time import should only be marked completed after it has actually drained existing V1 history, or it should notice new V1 rows and run/resume instead of reporting completed forever. At minimum, unimported V1 sessions should be surfaced instead of making session tabs 404.
Actual behavior
kvrowmigration.v1-v2 = {"phase":"completed"}, created 2026-09-17 10:19:31.- 40 V1 sessions existed in
session(versions 1.18.31 x11, 1.4.7 x20, 1.2.24 x9), 0 of them were copied tosession_v2. - All 19901 rows in the
eventtable still carry the defaultcreated = 0, suggesting thephase === undefinedbranch (clear events) did not do what it looks like it should on this V1 database. - Log: the client kept polling
GET /api/session/ses_...and got 404 while the row existed insession.
Additional context
Recovery/workaround that brought everything back without data loss:
- Back up
opencode.db. UPDATE kv SET value='{"phase":"sessions"}' WHERE key='migration.v1-v2';- Boot any V2 server (e.g.
opencode api --standalone get /api/experimental/migration/v1) so the importer resumes. - It copied all 40 sessions and converted 2211 V1 messages into
session_messagecorrectly, then wrote{"phase":"completed"}again.
So the import/conversion logic itself works; the completion/"already completed" gate is the problem. In the shipped binary the state is read with an early if (state.phase === "completed") return, and loose_psylocke already knows how to detect V1-only history (Previous V2 database contains V1-only session history), but there is no equivalent check or re-arm for V1 sessions newer than the last completed import.
OS
Arch Linux, kernel 7.2.6-arch2-1 (x86_64)
Terminal
OpenCode desktop app 2.0.10 (Electron); same behavior visible from the CLI (opencode api).
Activity
remorses commentedon Oct 6, 2026
Description
Confirmed on 2.0.19 while moving Kimaki from V1 to V2. The V1 history still exists, but V2 returns
SessionNotFoundError.My migration completed on September 12. I found 1,406 sessions created afterwards missing from
session_v2. One affected session, created October 6, still had 73 messages and 273 parts in the V1 tables.Plugins
Kimaki and kimaki-anthropic, plus the built-in plugins.
OpenCode version
2.0.19.
Steps to reproduce
session.getfor that ID. It returnsSessionNotFoundErroralthough the legacysessionrow exists.{"phase":"completed"}. Inpackages/core/src/database/v1-migration.bun.ts,run()returns immediately for that state.Screenshot and/or share link
I recovered the 1,406 late sessions after a verified SQLite backup, using
transformSessionfrom the published@opencode/core@2.0.19. Each import kept its session ID and used a separate transaction. I removed only the obsolete V1 events for the imported aggregates and set their converted sequence watermarks.The reported session now returns 81 converted history messages through the running V2 server. Backup comparisons found no deleted existing V2 sessions, messages, or events. I did not reset the global migration marker.
Operating System
macOS 26.3.1, Apple Silicon.
Terminal
Kimaki CLI and Discord.
janeklb commentedon Oct 12, 2026
Confirmed on macOS with OpenCode CLI 2.0.27 after previously using OpenCode Desktop alongside a V1 terminal installation.
Likely sequence
I installed Desktop around September 25 and believe it was a V2 build. I continued using V1 in the terminal afterward. On October 12, I removed the Desktop application and upgraded the terminal installation from 1.19.0 to 2.0.27.
A verified pre-upgrade database backup already contains:
kv['migration.v1-v2'] = {"phase":"completed"}I have not independently verified which Desktop build wrote the marker.
Read-only audit results
Comparing legacy
sessionrecords withsession_v2by session ID found:messagetableOne representative top-level session has 300 V1 message rows and 1,292 V1 part rows, but no
session_v2record and nosession_messagerows. Its dates and V1 row counts match the pre-upgrade backup.A separate check found no sessions created before the completion marker and already present in V2 with V1 message rows created after that marker. This does not establish complete content equality or rule out edits to older message rows.
Existing-backlog recovery question
PR #54272 appears to address future V1 additions while preserving existing V2 sessions. However, its current handling of an existing
completedmarker without a watermark records the current minimum legacy session ID and returns. As I understand it, that leaves an already-stranded backlog like these 69 sessions unconverted.Could maintainers provide a supported recovery command or migration for this existing-backlog case?
The recovery needs to preserve existing V2 sessions and messages, handle parent/child sessions, and avoid restoring sessions intentionally deleted in V2. A preview or explicit session-ID selection would help resolve that ambiguity.
I have kept the backups and have not reset or changed the migration marker, rerun full conversion, or restored an old database over the active database. I would prefer not to reset the marker because the 2.0.27 converter deletes existing V2 message rows before rebuilding them from V1.
I can provide additional anonymized metadata from read-only queries.
4sDriven commentedon Oct 13, 2026
Thanks for the audit — you read it right, I wrote #54272.
The conservative path was because a migrated-then-deleted session and a never-migrated one look identical in the DB: V2 deletes hard and never touches the V1 tables, so importing everything missing would resurrect sessions people deliberately deleted.
Your backlog is still recoverable. The migration marker in
kvis timestamped, and it gets rewritten as the migration walks sessions, so it lands on "finished scanning". Compare that with each legacy session's created time: before it and missing from V2 → migrated then deleted, leave alone; after it and missing → genuinely stranded, safe to import. Using the migration start instead is safer still, since re-importing is a no-op and anything V2 deleted predates the migration, so nothing resurrects. Only catch: read that timestamp before a version that stores a watermark rewrites it.If the maintainers are fine with it, I'll derive the boundary that way for existing completed states and add a dry-run listing what would be imported vs skipped.
What does
kvshow formigration.v1-v2on your DB? I'd like to check the split against your data.visuarte commentedon Oct 14, 2026
Confirmed duplicate — with a fully timestamped reproduction and a non-destructive recovery.
Environment: macOS, opencode 2.0.28 (desktop app; the homebrew CLI on the same machine was 2.0.25).
Timeline recovered from the DB
kv['migration.v1-v2'] = {"phase":"completed"};time_created2026-10-11 19:18:18,time_updated19:18:22 (local). The marker has no watermark on 2.0.28, i.e. it predates fix(core): import V1 sessions created after migration completes #54272.session_messagefor a session that did migrate: 19:18:20 — the marker lands on "finished scanning", as described in this thread.The "missing messages" case here is the same latch, not #53971
For a session created before the marker (15:37) that did migrate, the legacy tables hold 81 messages written after 19:18:20 (up to 19:56:03) that never reached
session_message. They are a contiguous tail — 0 of the 81 predate the last imported message — so in this instance "missing messages" is fully explained by the latch, not by rows dropped in schema validation. Legacy is a strict superset of V2 for that session (293 vs 213 messages). Worth checking whether #53971's path is a separate mechanism.Non-destructive recovery (the
kvmarker was never touched)Everything was recovered through the official path, so no marker reset (re: the warning in #49641):
sqlite3 .backupofopencode.dbfirst (pragma integrity_check→ ok).session/message/part) and runopencode session import <file> --directory <dir>. It preservesinfo.idandinfo.time.created, so the sessions come back as themselves.Traps in the import schema, for anyone else attempting this:
info.projectIDis required — take it fromsession.project_id.time.createdforreasoning/tool(legacy storestime.start/time.end).state.errormust be structured ({"type":"tool.execution","message":"…"}); legacy stores a plain string.UNIQUE constraint failed: session_message.id(surfaced as HTTP 500).time_updated = now, so every recovered session shows up as "today" in the list until you restore it from the legacy row.time.completedand cosmetic metadata (snapshot.files,state.attachments). Our reconstruction comes out a superset because of the 81-message tail.Selection criterion used
Matches the safe rule described above: all 9 were created after the marker and missing from
session_v2; none predates the marker, so no migrated-then-deleted session was resurrected.desaiamogh commentedon Oct 25, 2026
Confirming the same latch on Windows, with the V1 desktop app (bundled server 1.19.4) running alongside the V2 CLI (2.1.4): our
kvmarker completed Oct 14, and 5 sessions created in the V1 desktop afterwards stayed in the legacy tables only —GET /api/session/{id}404, stale desktop tabs — plus a ~380-message legacy-only tail on one already-migrated session. Details in #58840 (closing as a duplicate of this issue).Two notes that may help #54272:
session_messagetransformation from our DB and it reproduces the official importer's output exactly (parsed-JSON equality) on 650/650 already-imported messages. So the PR's import output can be diffed against existingsession_messagerows as a regression check.state.error/state.contentshapes that make hand-rebuilt rows fail the decoder) is posted on session: migrated session missing messages written after V1→V2 migration; export truncated #57000, which tracks the truncated-transcript half of this bug.daurge-fff commentedon Oct 28, 2026
OpenCode V1 → V2: Missing sessions after migration - diagnosis and successful recovery
I am sharing a detailed report of a V1 → V2 migration problem I encountered on macOS, including database diagnostics, failed recovery attempts, the final outcome, and recommendations for making recovery safer and easier for other users.
The goal is to document a practical troubleshooting workflow for users whose conversation history appears to be missing after upgrading to V2.
Important distinction: I successfully recovered two sessions, but I have not isolated the exact operation that ultimately caused the recovery to succeed. The commands below distinguish verified diagnostic steps from recovery experiments that require additional safeguards.
1. Environment
Operating system: macOS
Architecture: Apple Silicon (ARM64)
Application: OpenCode Desktop 2.2.0
Legacy session versions: 1.19.3 and 1.19.0
SQLite database:
~/.local/share/opencode/opencode.dbProject directory:
<project>The Desktop application's bundled CLI was located at:
The exact CLI location may differ between versions and installations.
2. Summary of the problem
After migration from V1 to V2, two important conversations were initially missing from the V2 interface.
However, the original session records and their associated messages were still present in the legacy SQLite tables.
The affected sessions were:
Session ID | Title | Legacy messages -- | -- | -- ses_e81b4289effevUUK53fcEktIu4 | Title 1 | 431 ses_ec563a93bffeAK3KWn03G6ZFYq | Title 2 | 200The original session IDs and titles were preserved.
The V2 message counts were one higher than the legacy counts for both sessions. I have not established the reason for this difference, so I am not claiming that the additional records are necessarily errors or that the counts should always match exactly.
The important outcome is that both conversations became available again and their histories were recovered.
I cannot yet identify the single operation that definitively caused the recovery to succeed. The successful outcome followed database inspection, failed JSON imports, migration investigation, and further recovery attempts.
I am documenting the entire sequence to help others investigate the same failure without assuming that one workaround is appropriate for every database.
13. What would make recovery much easier for users?
I believe OpenCode could improve this workflow in several ways.
A. Detect incomplete migrations
Before marking a migration as complete, verify that legacy sessions have either been imported or explicitly reported as skipped.
B. Provide a migration audit command
A diagnostic command could report:
Legacy sessions detected.
Sessions already present in V2.
Sessions missing from V2.
Messages that failed conversion.
Sessions skipped because of conflicts.
Any inconsistencies that need manual investigation.
Ideally, it would not expose conversation content or secrets.
C. Provide a dry-run recovery mode
Users should be able to see which sessions would be imported, skipped, or potentially duplicated before modifying the database.
D. Make migration resumable and idempotent
Rerunning migration should safely process missing records without duplicating or damaging existing V2 sessions.
E. Provide a supported session recovery command
A targeted recovery command would be safer than manually changing migration markers or inserting rows into SQLite.
F. Validate imports
If
session importrejects an export, the CLI should provide more specific information about the invalid field or structure rather than returning onlyBad Request.G. Document a safe recovery workflow
The official documentation should explain how to:
Back up the database.
Detect missing sessions.
Inspect the migration state.
Attempt supported recovery.
Validate the resulting database.
Preserve existing V2-native conversations.
14. Related reports and further investigation
These issues are relevant to the broader V1 → V2 migration problem:
server: V1 sessions created after migration.v1-v2 completes are never imported #50130 — V1 sessions created after migration completes are never imported.
migration: one-time V1-to-V2 session import misses sessions and messages created by V1 afterwards #58840 — Reports concerning missing V1 sessions after migration.
core: sessions written only to the V1 tables are invisible in V2 (404, missing from tabs) #55312 — Discussion of reconstructing legacy session exports and importing them.
V2: V1 session history is never imported into session_v2 — old sessions invisible in OpenCode 2 #41217 — Broader reports of missing history during V1 → V2 migration.
The precise behavior may differ by OpenCode version, so recovery instructions should be verified against the version being used.
Conclusion
The original session data was still present in the legacy database, and I was able to recover both missing conversations.
This experience suggests that a migration marked as completed should not automatically be treated as proof that all conversation history is accessible in V2.
I hope this report helps other users diagnose missing sessions, avoid unnecessary data loss, and recover their history safely. I would also appreciate guidance from the maintainers on the officially supported recovery procedure for sessions that remain in the legacy tables after migration.
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report. I reproduced this with a V1 1.18.31 server and V2 sharing one
opencode.db:completedand that session is imported.{"status":"completed"}, the new session is in the legacysessiontable but not insession_v2, andGET /api/session/<id>returns 404SessionNotFoundError.This happens on 2.0.10 and on the latest release, 2.2.0. I couldn't run the latest
dev(62ae125) from source, but its migration code still stops early once the state iscompleted. Open PR #54272 is aimed at exactly this case. The script below reproduces it.I didn't reproduce the other part, where the first run skipped the 29 V1 sessions that already existed: in my test the existing session was imported. #59517 may be related to that part.
Related: #57080 (PR for V1 messages written after the migration completed), #57000, #58846.
Reproduction script (repro.sh)
Run it:
bash repro.sh