Skip to content

Design: store generation detection for 1.18→2.x migrated opencode stores (backs PR #486) #489

Closed

Description

@DGVY

Context

OpenCode's official 1.18 → 2.x in-place upgrade keeps the retired message/part tables beside the new session_v2/session_message ones. detectOpenCodeStoreGeneration is keyed on v1-table presence, so every migrated store reads as v1 — and the v2 fail-closed assert then refuses generation-specific access on every read. On opencode 2.0.5/2.0.8 hosts this surfaces as every prompt interrupting within ~20 ms (session hook dies during setup, host swallows the error, zero log lines). Full incident write-up in PR #486.

Design decisions in PR #486

D1 — session_v2 table presence is the strongest v2 signal, checked first.
1.18.x never creates session_v2; every 2.x store has it from first write. The v1 heuristic (message+part) stays as the fallback for pure 1.18.x stores. Consequence: migrated stores (both table sets) classify as v2, which is correct — the v2 readers are the ones that must own them.

D2 — the tool-owner backfill gates on what it reads, not on a single generation.
The backfill reads the v1 message/part tables, which exist in pure v1 stores and in migrated stores. Asserting expected "v1" (the pre-existing hard-code) throws on migrated stores at normal database open, silently degrading every eligible legacy row to the slower lazy-adoption fallback — flagging exactly the stores that most need backfill (tens of thousands of legacy rows). New hasV1MessageTables(db, schema) helper: tables present → backfill runs; absent (pure v2) → fail fast with a clearer message.

Alternatives rejected

  • Tolerate both generations in every reader (no detection change): spreads "v1-or-v2" branching through every query site; the fail-closed guard exists precisely to prevent that.
  • PRAGMA user_version bump: the opencode host owns that schema; a plugin-side convention on host-owned metadata is fragile across host versions.
  • Row-count heuristics (e.g. "v2 tables non-empty → v2"): a migrated store that upgraded before writing any v2 row would misclassify; table existence is decided by DDL, not workload.

Verification

  • Unit tests cover all four store shapes: pure v1, pure v2, migrated (both table sets), fresh/unknown — pinned live-1.18 fixture unchanged and passing.
  • shared + v2 + features suites: 2197 pass / 1 fail, the failure (transformers-node-wasm fixture, network model build) reproduces on unmodified master under the same sandbox.
  • Exercised on a real migrated store (~46k legacy message rows, opencode 2.0.8 host): pre-patch every prompt died in <20 ms; post-patch the full chain (session hook, compaction, memory) is healthy.

Requesting design-approved so PR #486 can proceed past the design gate. If maintainers consider this a trivial fix, the gate also accepts the trivial label on the PR itself — either way works.

Activity

  1. magic-alfonso commented on Sep 20, 2026

    @magic-alfonso

    Thank you for laying out D1 and D2 explicitly. The schema evidence supports a narrower conclusion than D1: session_v2 is an unambiguous marker that OpenCode 2 has touched the store, but not an unambiguous marker of the current writer. In the downgrade case the table remains while OpenCode 1 resumes writing message/part, so a global session_v2 => v2 rule rejects active v1 readers and can select frozen v2 rows.

    The decision is #477's approach for the runtime defect, now on master (2c034c3): keep mixed-store detection conservative and allow an explicitly expected v2 reader when both session_message and session_v2 exist. That fixes the migrated OpenCode 2 prompt interruption without breaking downgraded stores.

    D2's capability-based backfill gate is sound and we will carry it as a separate follow-up with credit to you. It is hardening rather than a prerequisite, because #477 leaves mixed detection as v1 and the existing backfill assertion already passes on that shape.

    We will also follow up on doctor's table selection: #477's host override is wrong in the pre-migration upgrade window, and its fallback does not cover migrated Desktop installations. That diagnostic needs to combine host and store state, since table presence alone cannot distinguish a migrated v2 store from the same store after a downgrade. This issue stays open until the D2 follow-up lands.

  2. DGVY commented on Sep 20, 2026

    @DGVY
    Author

    Agreed on D1 — your downgrade analysis is exactly right, and I can confirm it with a live case: this reporter's machine just went through that path (OpenCode 2.0.8 → 1.18.31 rollback last night, with message/part restored beside the retained session_message/session_v2). My global session_v2 => v2 rule would have rejected the active 1.18 v1 reader on exactly this shape. #477's two-channel approach (conservative detect for expected-v1 readers, widened acceptance only for explicitly-expected-v2 readers) is the correct shape — closing #486 as superseded.

    D2 as a credited follow-up works for us; the capability-based gate composes cleanly on top of either detection path. Happy to review the follow-up when it lands.

  3. magic-alfonso commented on Sep 24, 2026

    @magic-alfonso

    Implemented in 0.43.0, released today, with credit. Store generation detection now handles OpenCode stores migrated from 1.18 to 2.x, using the capability-gate shape from PR 486, and doctor picks its boundary table from the resolved host generation combined with the store schema. Thanks for the design and the live downgrade case.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions