Repository navigation
Description
Description
The V1 → V2 session migration projected only user messages for older sessions into the new session_message table. All assistant messages were skipped with reason=invalid-message and stayed in the legacy message/part tables, where the V2 client, API and session export no longer see them.
Result after migrating a large V1 install: of 336 sessions, 127 have fewer messages in V2 than in V1 — 9,353 messages missing from the V2 view. In 126 of those sessions there are zero assistant messages, so an old session renders as a bare list of user prompts.
The pattern is the V1 message shape, not the session: rows that already used the newer V1 shape (agent, model, summary) migrate fine; rows in the classic V1 assistant shape (parentID, providerID, modelID, mode, path, cost, tokens, finish) are rejected.
Concrete example (ses_29790b6c9ffeu67nLM0EU5zTdT):
legacy message |
V2 session_message |
|
|---|---|---|
| assistant | 752 | 0 |
| user | 60 | 50 |
| synthetic | – | 5 |
opencode session export ses_29790b6c9ffeu67nLM0EU5zTdT returns exactly those 55 records (50 × user, 5 × synthetic) — no assistant content at all.
The data itself is not lost, only unprojected: the assistant text is still complete in the legacy part rows (21,335 × text, 16,425 × reasoning, 31,367 × tool parts) and in ~/.local/share/opencode/storage/message/*.json.
Log evidence (one migration run, 2026-10-14T11:27, run=80841d95):
level=WARN message="Skipped V1 migration row" reason=invalid-message sessionID=ses_4fff14cd0ffeFqBQMcSf47Skxx messageID=msg_b000eb371001hUYBzY2ry54wiy role=server
9,333 such lines across 128 session IDs; the migration still reports itself as complete (entry in the migration table), so it never retries. Still reproducible on 2.0.30 — the upgrade from 2.0.29 changed nothing, because the migration is one-shot.
Steps to reproduce
- Start from a V1 install whose sessions contain assistant messages in the older V1 shape (pre-
agent/model, i.e. withparentID/providerID/modelID/finish). - Update to V2 and start it, letting the V1 → V2 migration run.
- Open an old session in the TUI, or run
opencode session export <session-id>. - Check
~/.local/share/opencode/log/opencode.logforSkipped V1 migration rowand compare row counts:
SELECT count(*) FROM message WHERE session_id=?vs.SELECT count(*) FROM session_message WHERE session_id=?. - Observe: only user prompts are visible, assistant replies are missing.
Expected behavior
The V1 → V2 session migration projects the full history of existing sessions, so old sessions keep their assistant replies in V2 — the same way it already handles messages in the newer V1 shape. If some rows genuinely cannot be converted, they should not be silently dropped from the V2 view without a recoverable path.
Actual behavior
For 127 older sessions, all assistant messages are absent from session_message and therefore from the TUI/API/export. They are only reported as WARN "Skipped V1 migration row" reason=invalid-message and remain readable solely through the legacy tables.
Additional context
- Version: 2.0.30 (first observed on 2.0.29)
- Database:
~/.local/share/opencode/opencode.db—session_message26,601 rows, legacymessage32,573 rows,part130,767 rows - Not an orphan/corruption problem: 0 sessions without messages, 0 orphaned messages, 0 broken
parent_idreferences. Session persistence,session list,session export, child sessions and restarts all behave correctly — only the historical assistant content is missing from V2. - The DB file also carries ~604 MB of free pages after the migration (
freelist_count), which may be related if rows are rewritten during the same run.
Activity
opencode-agent commentedon Oct 16, 2026
Thanks for the detailed report and the row counts. I think this is a duplicate of #53971. There, the V1 → V2 migration checks each legacy message against the current V1 schema, which requires
agent(plusparentIDon assistant messages andmodelon user messages). Messages written in the older V1 shape (mode/modelID/providerID/path/cost/tokens, noagent) fail that check. They are skipped withreason=invalid-message, and the migration is still marked as complete. That matches what you're seeing. PR #53996 is open to fill in those fields during migration instead of dropping the rows.Possible duplicates and related issues, ordered from most to least likely:
agent/model/parentIDare dropped withinvalid-message); reproduced, with a fix in fix(core): migrate V1 messages that predate required metadata #53996agentfield fails schema validation — 6,238 messages across 92 sessions #52226: same problem (older assistant messages missingagent). It was auto-closed only for not following the template. Its comments have a recovery procedure (copymodeintoagent, then re-run the migration) that was run on a real database, plus a note on the large freelist the migration leaves behindIf you try one of the manual workarounds, back up
opencode.dbfirst. As noted in #53971 and #52226, re-running the migration can overwrite V2 history that was added to migrated sessions after the first run.The bot will close this issue as a duplicate in 1 day unless you reply explaining how your problem is different.
skquievreux commentedon Oct 16, 2026
Thanks — agreed, this is the same root cause, and closing it as a duplicate of #53971 is fine. Two data points from a second, independent database (Windows, migrated 2026-10-14 on 2.0.29, unchanged on 2.0.30) in case they help #53996:
agent IS NULL— and all 9,330 carrymode(build8121 /plan665 /explore514 /general30). No assistant row is missingparentID, and no user row is missingagent/model. So on this database theagent = moderecovery alone is sufficient; theparentIDand user-modelderivations in fix(core): migrate V1 messages that predate required metadata #53996 are never exercised.invalid-messagelines vs. 9,330 rows derived from the DB — no lines dropped under load (unlike the undercount reported in V1→V2 migration silently drops v1.0.x-era assistant messages: requiredagentfield fails schema validation — 6,238 messages across 92 sessions #52226).One gap that #53996 does not close: it changes the migrator, but the migration is gated by
kv['migration.v1-v2'], which reads{"phase":"completed"}on every install that has already migrated. Updating 2.0.29 → 2.0.30 re-projected nothing here, so a fixed migrator will not repair an existing database on its own — those users still have to delete that marker to force another run.If that manual reset is the intended remedy, could it be stated in the release notes or migration docs, next to the existing warning that a re-run wipes V2-appended history (#52226)? A one-line note would save every affected user from repeating this same research.
opencode-agent commentedon Oct 16, 2026
Thanks for confirming, and for checking against the database rather than the log. Knowing that every affected row on your install has
modeand onlyagentis missing helps with #53996.You're right about the gate. On current
dev,packages/core/src/database/v1-migration.bun.tsreturns early wheneverkv['migration.v1-v2']hasphase: "completed", and #53996 only changes how rows are converted. It doesn't re-check or re-run installs that have already migrated. As it stands, the fix alone wouldn't repair existing databases, and the only way to get those rows back is still a manual reset with the warning about V2 history added after the first run (#52226).This issue is going to close as a duplicate. Please post that point, and the request for a release-note or migration-docs entry, on #53971 or PR #53996, where maintainers and the PR author are working on the fix. I can't say whether they'll handle it with a one-time re-run, a migration that repairs only the affected sessions, or documentation.
The bot will still close this issue as a duplicate of #53971 in 1 day.
opencode-agent commentedon Oct 17, 2026
Closing this as a duplicate of #53971, as agreed above. Please follow #53971 and PR #53996 for the migration fix.
If you haven't yet, please post your point there about installs that are already marked
migration.v1-v2: completed. Those won't be re-projected by a fixed migrator on their own, so it may need a one-time re-run or a note in the docs. The people working on the fix will see it there. Thanks again for the detailed counts from your database.