fix(opencode): read opencode sqlite storage#108
Merged
Conversation
Generated by driving the real opencode CLI in sandboxed XDG dirs at a neutral path: 10 sessions (1 task-tool child, 1 archived), 27 messages, 69 parts covering text, reasoning, file, patch, and tool parts in completed and error states. One doctored row replicates the Feb-2026 migration quirk (time_created column months ahead of data.time.created). Secret-scanned (trufflehog, gitleaks) and json-validated; procedure and coverage gaps documented in the README.
opencode stopped writing the JSON file tree in v1.2.0 (2026-02-14); sessions live in opencode.db (WAL, Drizzle). The adapter read only the dead tree, so current installs synced nothing new (reported: 1 session against a 3.0 GB db).
The adapter root is now the opencode data dir (a configured path ending in storage/ normalizes to its parent). It reads every opencode*.db read-only alongside the legacy tree, deduping by session id - DB wins, tree fills the stranded pre-v1.2 gaps, skips counted. Records rebuild exactly as opencode hydrates them ({...data, id, sessionID(, messageID)}) and feed the existing part pipeline; session rows project to the fromRow SessionInfo shape as raw_record. Freshness watermarks come from the newest message's data.time.created plus tool state.time.end - never the time_created column, which migration-copied rows stamp with the migration time. Child sessions (parent_id set) are labeled opencode/<agent> so they leave default search like other subagents.
Verified against a live data dir: 59/59 sessions ingest (was 25 stale tree files), second sync is a freshness no-op.
The file-tree serializer targeted a format opencode no longer reads. Native and foreign restore now emit one <session_id>.json per session in the {info, messages:[{info, parts}]} envelope that opencode import accepts. Native replays stored raw_records into the envelope and skips the synthetic split records, re-fusing into the single source tool part; foreign builds minimal idiomatic info shapes from canonical fields, re-fusing ToolCall into a completed tool part. Parent and child sessions each get their own file (adapter-lineage-complete-restore).
Conformance is tested against the DB fixture: every emitted info/message/part value-equals the independently row-reconstructed JSON. The envelope shape was verified against the real CLI: opencode 1.17.15 import accepts it and lands the session in a fresh db.
tenequm
marked this pull request as ready for review
July 14, 2026 12:35
read_bench: the crate instantiates the lib's deep async ingest futures, whose layout computation exceeds rustc's default query-depth limit of 128; raise the crate recursion_limit to 256. commands_bench reads peak RSS via libc::getrusage under cfg(unix), but libc was declared as a macOS-only dev-dependency, so the bench target failed to resolve on linux. Scope the dev-dependency to cfg(unix), which still covers the macOS phys_footprint sampling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QkXo1o4Cj7LhBL6LiEMrMq
Review-driven simplification of the opencode sqlite adapter, plus the framework support it needs: - New Superseded skip class end-to-end (SkipReason -> SyncStatus -> IngestSummary.skipped_superseded -> sync output and ingest_bench): a legacy tree copy whose session id exists in a DB is dropped visibly per spec adapter-integrity-dedup, never folded into Empty. Supersession is by id - the adapter's documented contract; content identity is not verified. - Enumeration restructure: light SELECT-id enumeration with one shared read-only connection per DB; the full session row is fetched by primary key only for freshness-gate survivors. Per-source failures degrade to a warning plus visible Err events; per-session peek failures degrade to a safe re-read. plan()/--dry-run degrade the same way. discover() returns the true deduped count, removing the progress-bar compensation arithmetic. - SessionRow and its positional twins replaced by one name-based session_info_from_row; watermark fold hoisted into a single watermark_micros kernel with its residual documented. - Point fixes: session-anchor-only timestamp fallback, foreign-format downgrade instead of dropping raw-record-less messages, DB faults classified as io, shared by_timestamp_then_id ordering, message fetch sorted in Rust instead of a temp b-tree, Entry-based connection cache. - Shared helpers hoisted: MaxWatermarkOracle into test_support, json_or_string into extract (adopted by codex_cli). - Tests: fixture-derived expected envelope replaces the tautological round-trip, shared plan-gate helper, dedup asserts the superseded count and skipped_empty == 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QkXo1o4Cj7LhBL6LiEMrMq
…nly watermark probe
Install the rust-toolchain.toml pin explicitly and serially instead of letting it materialize on first cargo use - moon fans out 5 cargo tasks in parallel, so five rustup installs raced on one download dir and clobbered each other. A warm /ci-cache hid it; any cold cache failed the build. Same class of bug, also fixed: flock the whole bootstrap (runner pods share one RWO /ci-cache and bootstrapped into the same paths concurrently), and stage every tool to a temp dir then land it via atomic rename so a cancelled run cannot leave a half-written binary behind.
This was referenced Jul 14, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
opencode moved session storage from the JSON file tree to SQLite (
opencode.db) in v1.2.0 (2026-02-14) and removed the JSON-to-SQLite startup migration on 2026-06-02. pond's adapter read only the dead tree, so current installs sync almost nothing (reported by Fabian/xeroc: "1 sessions, 8 messages, up to date" against a 3.0 GBopencode.db).Full implementation contract:
docs/plans/2607-14-opencode-sqlite-adapter.md(grilled decisions locked 2026-07-14).What
~/.local/share/opencode); a configured path ending instorage/normalizes to its parent, so existing configs keep working.opencode*.db(rusqlite, bundled, read-only URI mode, 5 s busy timeout, short per-session read bursts - WAL-checkpoint safe) plus the legacystorage/tree for stranded pre-v1.2 sessions. Dedup by session id: DB wins, tree fills gaps, skipped duplicates are counted, not silent.{...data, id, sessionID(, messageID)}, parts ordered by id, feeding the existing part pipeline (fused-tool split, provenance, bounded values, record cap). Session rows project to thefromRowSessionInfoshape asraw_record.data.time.created+ toolstate.time.end- never thetime_createdcolumn, which migration-copied rows stamp with the migration time (verified on a real row: column 2026-06-24 vs data 2025-10-07).parent_idgetsource_agent = opencode/<agent>, leaving default search like claude-code subagents.<session_id>.jsonper session in the{info, messages:[{info, parts}]}envelopeopencode importaccepts (the dead tree serializer is gone). Native replays storedraw_records and re-fuses the synthetic tool split; foreign builds minimal idiomatic shapes from canonical fields. Parent and child each get their own file.tests/fixtures/adapter/opencode/opencode.db(496 KB) produced by driving the real opencode 1.17.15 CLI in sandboxed XDG dirs at a neutral path; one documented doctored row replicates the migration-stamp quirk. Secret-scanned (trufflehog + gitleaks clean). Legacy tree fixture retained for the stranded-JSON path. Coverage gaps (subtask/agent/compaction/retry/snapshot part types, unreachable via non-interactiveopencode run) documented intests/fixtures/README.md; carrier injection for them is covered by tests that insert hand-authored rows.Verification
~/.local/share/opencode: 59/59 sessions ingest (previously 25 stale tree files, all deduped away as migration copies), 2,199 messages, search/get return DB-era content, second sync is a freshness no-op.opencode import(1.17.15) in a sandbox: accepted, session lands in a fresh db.message_session_time_created_id_idx,part_message_id_id_idx) - no full scans on multi-GB DBs.Follow-ups (not this branch)