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.
Context
OpenCode's official 1.18 → 2.x in-place upgrade keeps the retired
message/parttables beside the newsession_v2/session_messageones.detectOpenCodeStoreGenerationis 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_v2table 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/parttables, which exist in pure v1 stores and in migrated stores. Assertingexpected "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). NewhasV1MessageTables(db, schema)helper: tables present → backfill runs; absent (pure v2) → fail fast with a clearer message.Alternatives rejected
PRAGMA user_versionbump: the opencode host owns that schema; a plugin-side convention on host-owned metadata is fragile across host versions.Verification
shared+v2+featuressuites: 2197 pass / 1 fail, the failure (transformers-node-wasmfixture, network model build) reproduces on unmodified master under the same sandbox.messagerows, 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-approvedso PR #486 can proceed past the design gate. If maintainers consider this a trivial fix, the gate also accepts thetriviallabel on the PR itself — either way works.