anomalyco / anomalyco/opencode

V1→V2 migration loses/hides history: corrupt-part migrator 500s, undecodable rows report no field detail, >9k-message imports 500 with empty body, project picker is a hidden shortcut

Open
#42,671 2 comments 0 reactions 1 assignee View on GitHub

@jlongster is already working on this.

Since Aug 15, 2026.

2.0
Dominant language
TypeScript
Stars
209k
Forks
27.5k
Avg merge
7h 2m
Merged PRs (30d)
384

Description

Summary

Migrating OpenCode V1 chat history into the V2 desktop beta destroys or hides it: the built-in migrator 500s on a few corrupt legacy rows, sessions written by third-party tools into the V2 tables are silently undecodable, very large sessions can't be imported at all, and there is no discoverable way to open the old sessions (project picker exists only as a hidden keybind).

Environment

  • opencode version: 0.0.0-next-17403 (embedded opencode-cli serve --service via OpenCode Beta desktop app), also opencode2 0.0.0-next-17428
  • OS: Linux 6.8.0-136-generic (Ubuntu), x86_64
  • Terminal: xterm-256color (desktop app is the client used)
  • Shell: /bin/bash
  • Install/channel: next / beta desktop (Electron, /opt/OpenCode Beta/ai.opencode.desktop.beta)
  • Active plugins: none found in config

Reproduction

  1. Start from a V1 data dir (~/.local/share/opencode/opencode.db containing legacy session/message/part tables alongside V2 session_v2/session_message), with 26 corrupt legacy part rows.
  2. Open the V2 desktop beta; let it run its built-in migration.
  3. GET /api/experimental/migration/v1500 database disk image is malformed (querying message by a session whose part rows are corrupt). No partial credit, no user-facing warning — history just looks empty.
  4. Any tool or script that "helps" by writing V1-shaped rows directly into session_v2/session_message produces rows that SELECT cleanly but fail the runtime decoder:
    GET /api/session/<id>/message500 "failed to decode session message" (log only).
  5. POST /api/session/import with a session of ~13k messages → 500 with empty body; log shows
    SQLite query expected 27270 values, received 92806 at SessionTransfer.import (../server/src/handlers/session.ts:112). Last safe observed size: 9000 messages.
  6. Even after messages are present and decoding, the beta desktop home/history shows nothing for the project — the project picker (project.select) exists only as a hidden keybind mod+shift+o (not in Settings), and in the current desktop build that shortcut does nothing when pressed. lastProject stays pinned to whatever was open on restart, so there is no working way to reach another folder's history from the UI.

Expected Behavior

  • Migration should survive a few bad legacy rows (warn + skip that session) and report what failed.
  • Records written in a compatible format should either decode or fail with the specific field (the import validator already does this well).
  • History, once migrated, should be reachable from the UI without a hidden shortcut.

Actual Behavior

  • One corrupt part row aborts the whole migration; the app shows empty history with no error.
  • Hand-migrated rows are rejected with only failed to decode session message and no field detail (root causes we found: missing top-level finish, missing model.variant, step-start/step-finish content items that don't exist in V2, tool state using output instead of content, tool error states needing state.error as a Session.StructuredError object).
  • Sessions >~9k messages cannot be imported (prepared-statement parameter mismatch → silent empty-body 500).
  • Migrated history is effectively invisible: GET /api/session defaults to newest-50 and the app has no discoverable session/folder picker — the only candidate (project.select / mod+shift+o) is declared in the command catalog but non-functional in the desktop build.

Additional Context

  • Expected import payload shape that works: { "info": Session.Info, "messages": Session.Message.Info[] } — verified end-to-end (1000+ sessions imported, all decode, served via the message endpoint).
  • Frequency: consistently reproducible across restarts.
  • Workaround used: delete the offending V2 rows and re-import each session through POST /api/session/import, converting V1 part data to the strict V2 shapes; for the two ~13k/18k-message sessions, only the most recent 9000 messages could be imported.
  • Idempotency note: an import whose POST returns empty-body 500 looks like success to clients; ?.data and error tags are not consistently enforced.
  • No opencode git checkout was available on the machine, so no exact source refs were inspected; stack path in step 5 came from the server log.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.