Skip to content

session: V1→V2 importer skips sessions created after first V2 launch #49641

Closed as duplicate

Description

@gclawes

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

  1. Install and use OpenCode 1.x long enough to have session history in ~/.local/share/opencode/opencode.db (session / message / part).
  2. Launch OpenCode 2 (beta or 2.0.x) once. Confirm GET /api/experimental/migration/v1 returns {"status":"completed"} and kv.migration.v1-v2 is {"phase":"completed"}.
  3. Continue using OpenCode 1.x (for example 1.18.27 / 1.18.30) in the same data directory. Create new sessions.
  4. 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-18999 at 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 session and session_v2
  • All 7 V1 sessions created after that timestamp exist only in session
  • GET /api/session/{id} for those 7 IDs returns SessionNotFoundError (HTTP 404)
  • GET /api/experimental/migration/v1 still 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_v2 row, no session_message rows

V2 session lists (global and project/directory) therefore omit it.

Additional Context

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

  1. github-actions commented on Sep 18, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  2. drsmile1001 commented on Sep 19, 2026

    @drsmile1001

    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-v2 to force a re-run is not safe. From a cleared state the importer deletes the entire event table before it copies anything.

    Where V1 kept running after the marker was set, every row in event belongs 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_sequence must not be lowered to the projection watermark

    The importer upserts event_sequence.seq to transformSession's watermark, which is only safe because it has already emptied event.

    For an aggregate whose events survive, event has a unique index on (aggregate_id, seq) and new events publish at latest + 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 in session_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 completed while sessions are missing

    GET /api/experimental/migration/v1 answers from the KV marker alone and never compares session against session_v2, so anything built on it reports a healthy migration. Returning required whenever 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 event wipe, and leave event_sequence alone for aggregates that still have events. That also makes the job re-entrant, which the mixed V1/V2 window requires.

    Workaround

    transformSession from @opencode/core/database/v1-migration.bun is exported as a pure function, so the missing sessions can be recovered out-of-band: copy the set difference into session_v2 and rebuild session_message from message and part through it, leaving event, event_sequence and the marker untouched. Run under bun, since the node condition resolves to a noop build. Snapshot with .backup and stop the service before writing.

    Environment

    • opencode 2.0.8, installed with the official curl install script
    • Fedora Linux 44, x86_64
  3. edyfox commented on Sep 24, 2026

    @edyfox

    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 after
    an earlier V2-beta trial were absent from the V2 view. All present in the legacy
    session table with full message/part transcripts.

    Root cause confirmed at the code level (bundled migration in the 2.0.15 binary):
    the event table is purged only when the kv.migration.v1-v2 row is absent
    (u === void 0). With the row present as {"phase":"sessions"}, the import re-runs
    and the purge is skipped. So a sticky {"phase":"completed"} marker is precisely
    what strands V1 sessions written after it.

    Safe recovery (verified, zero data loss):

    1. Back up the DB first (SQLite online backup API — safe while the server runs).
    2. Reset the marker — do not delete it:
      INSERT INTO kv (key,value,time_created,time_updated)
      VALUES ('migration.v1-v2','{"phase":"sessions"}', <now_ms>, <now_ms>)
      ON CONFLICT(key) DO UPDATE SET value=excluded.value, time_updated=excluded.time_updated;
      Deleting the row instead triggers the destructive event purge. Reset, never
      delete.
    3. Trigger the migration without killing the live session: start a throwaway
      opencode serve on another port against the same DB, wait for the marker to
      return to completed, then stop it.

    Result: session 156 → session_v2 158 (156 V1 + 2 native V2), 0 missing,
    0 duplicate ids across session_v2 / session_message / part, event intact
    at 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 report completed from
    GET /api/experimental/migration/v1 only when no unmigrated V1 rows remain. The
    marker-reset procedure above would also make a good documented repair path.

  4. neriousy commented on Oct 20, 2026

    @neriousy
    Member

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions