Skip to content

V1→V2 migration silently drops v1.0.x-era assistant messages: required agent field fails schema validation — 6,238 messages across 92 sessions #52226

Closed as not planned

Description

@hallelujah-shih

Status: recovered on the reporter side (2026-09-30). ⚠️ Correction: the true loss is 6,238 assistant rows across 92 sessions — the 5,544/77 figures below are from the skip log, which dropped 694 lines under load (the log is a lower bound, not an audit trail). Full root-cause chain (verified against V1 source tags), the second bug found during recovery (re-running the migration destroys V2-appended history), the validated recovery runbook, and production results are in the comments below. The numbers in this body remain the original report.


Summary

The V1→V2 data migration (v2.0.19, first launch) silently dropped 5,547 message/part rows across 77 sessions (5,544 messages + 3 parts), logging only a WARN per row (Skipped V1 migration row). Every affected session dates from the v1.0.x era (Nov–Dec 2025) and most lost 91–99% of their messages to the V2 projection. The raw data is still intact in the legacy V1 tables, but the migration state is marked completed, so the content is now unreachable from the V2 UI.

Root cause (verified)

SessionV1.Assistant (packages/schema/src/v1/session.ts:465) declares agent: Schema.String as required. Assistant messages persisted by v1.0.x contain no agent key (the field was added in later V1 releases), so transformSession in packages/core/src/database/v1-migration.bun.ts (line ~232, Schema.decodeUnknownOption(SessionV1.Info)) fails validation and the row is skipped with reason=invalid-message.

Verification on my database:

  • Sampled 200 skipped messages and checked them field-by-field against the Assistant struct: 200/200 are missing exactly one field — agent. All other fields (role, time, parentID, modelID, providerID, mode, path, cost, tokens, finish) validate cleanly.
  • All 77 affected sessions carry V1 version stamps 1.0.107–1.0.152; zero sessions from >=1.1 are affected.

Example skipped row (v1.0.126 assistant message, everything else valid):

{"role":"assistant","time":{"created":1764728238660,"completed":1764728251759},"parentID":"msg_...","modelID":"grok-code","providerID":"opencode","mode":"build","path":{"cwd":"...","root":"..."},"cost":0,"tokens":{"input":39843,"output":30,"reasoning":761,"cache":{"read":832,"write":0}},"finish":"tool-calls"}

Impact

Total skipped: 5,547 rows (5,544 invalid-message + 3 invalid-part) = 2.8% of all 198,228 V1 messages, concentrated in 77 old sessions. Top 15:

session v1 version created v1 msgs skipped v2 proj loss
ses_51e000dd4ffecZExS70p7re8TW 1.0.126 2025-12 465 447 17 96.1%
ses_53cef287fffea6L8QL0Vh36lc0 1.0.114 2025-11 437 425 12 97.3%
ses_545a1fb14ffe2p4XUYntmcE1DW 1.0.110 2025-11 321 313 10 97.5%
ses_536397eb6ffe7ppv4fRbJ52LKy 1.0.119 2025-11 219 213 6 97.3%
ses_528071fb1ffeu5FLtJhvAhnsYy 1.0.123 2025-12 186 180 6 96.8%
ses_54603e939ffe9md4O51n41r5Ez 1.0.110 2025-11 178 173 7 97.2%
ses_5421ef5e4ffe3eNE14CQ9Ex1Zm 1.0.114 2025-11 795 172 621 21.6%
ses_540b71d94ffegyqnqonRHseB9Y 1.0.114 2025-11 165 159 9 96.4%
ses_4da50572affeU1nGJbIQ7sZekp 1.0.152 2025-12 173 159 16 91.9%
ses_5351f23cbffe62TLi08jLFeOzH 1.0.119 2025-11 151 147 3 97.4%
ses_5021187f5ffeZSi7MgERXPJYAi 1.0.134 2025-12 150 142 9 94.7%
ses_51c106d0fffeI0Xzrp4Vejy1kS 1.0.126 2025-12 142 139 4 97.9%
ses_51b8dcae3ffeCgRgl8vn4ZZyhy 1.0.129 2025-12 126 125 2 99.2%
ses_536789327ffeJYZhvmrEuWBYws 1.0.119 2025-11 131 124 7 94.7%
ses_51e276fe3ffes7GBQnryZjA1hO 1.0.126 2025-12 125 123 3 98.4%
Full list of all 77 affected sessions
session v1 version created v1 msgs skipped v2 proj loss
ses_51e000dd4ffecZExS70p7re8TW 1.0.126 2025-12 465 447 17 96.1%
ses_53cef287fffea6L8QL0Vh36lc0 1.0.114 2025-11 437 425 12 97.3%
ses_545a1fb14ffe2p4XUYntmcE1DW 1.0.110 2025-11 321 313 10 97.5%
ses_536397eb6ffe7ppv4fRbJ52LKy 1.0.119 2025-11 219 213 6 97.3%
ses_528071fb1ffeu5FLtJhvAhnsYy 1.0.123 2025-12 186 180 6 96.8%
ses_54603e939ffe9md4O51n41r5Ez 1.0.110 2025-11 178 173 7 97.2%
ses_5421ef5e4ffe3eNE14CQ9Ex1Zm 1.0.114 2025-11 795 172 621 21.6%
ses_540b71d94ffegyqnqonRHseB9Y 1.0.114 2025-11 165 159 9 96.4%
ses_4da50572affeU1nGJbIQ7sZekp 1.0.152 2025-12 173 159 16 91.9%
ses_5351f23cbffe62TLi08jLFeOzH 1.0.119 2025-11 151 147 3 97.4%
ses_5021187f5ffeZSi7MgERXPJYAi 1.0.134 2025-12 150 142 9 94.7%
ses_51c106d0fffeI0Xzrp4Vejy1kS 1.0.126 2025-12 142 139 4 97.9%
ses_51b8dcae3ffeCgRgl8vn4ZZyhy 1.0.129 2025-12 126 125 2 99.2%
ses_536789327ffeJYZhvmrEuWBYws 1.0.119 2025-11 131 124 7 94.7%
ses_51e276fe3ffes7GBQnryZjA1hO 1.0.126 2025-12 125 123 3 98.4%
ses_53771461affe4CnPSNJbunUzdL 1.0.119 2025-11 127 118 7 92.9%
ses_4fe4f09c9ffeuvfd1SGhMBrlqG 1.0.134 2025-12 141 113 33 80.1%
ses_503f00988ffeoK3algD9u4lgVi 1.0.134 2025-12 103 100 3 97.1%
ses_52722b547ffedVFTLSa3zciWQD 1.0.123 2025-12 102 98 4 96.1%
ses_54a8a7505ffeFXnYtFntTE5MkM 1.0.107 2025-11 262 95 13 36.3%
ses_53545dcf3ffewWY8BRiZGmjcp9 1.0.119 2025-11 100 95 5 95.0%
ses_502333976ffe9pqlkjYcy3ksgi 1.0.134 2025-12 99 94 5 94.9%
ses_536b90f61ffeWJLtkV3XMGwWcQ 1.0.119 2025-11 88 87 1 98.9%
ses_52729b716ffeQSBGAZOd7MlZdu 1.0.123 2025-12 89 86 3 96.6%
ses_537989628ffe11jEpqeUNJEI0R 1.0.119 2025-11 82 79 4 96.3%
ses_4db01d874ffexK87fk2rt3bJ2V 1.0.152 2025-12 89 78 15 87.6%
ses_5422ef80dffeV1tsdRbnaLN99I 1.0.110 2025-11 77 75 2 97.4%
ses_5407e955affe9cJllWRRfPndLu 1.0.114 2025-11 77 74 3 96.1%
ses_54089c199ffef8Gc0xih7kdj2W 1.0.114 2025-11 71 69 2 97.2%
ses_53692e872ffe35A6pzOMeCSflC 1.0.119 2025-11 70 68 2 97.1%
ses_540505ba1ffeli7Xa1h5J7wtvD 1.0.114 2025-11 69 66 3 95.7%
ses_536c818e0ffezFb2wgJElolpsG 1.0.119 2025-11 67 64 3 95.5%
ses_5403d1ce8ffeB9j9WDjjEH7pE7 1.0.114 2025-11 66 62 4 93.9%
ses_535a63703ffeKhjDUy0Lkr6XbO 1.0.119 2025-11 60 59 1 98.3%
ses_51cf532e6ffekHNcXBPa6lIfjG 1.0.128 2025-12 63 58 6 92.1%
ses_50381f9e2ffenWzH7pXjC7pt7H 1.0.134 2025-12 59 57 2 96.6%
ses_503ff21f4ffeamKQcBUwruuHDB 1.0.134 2025-12 58 55 3 94.8%
ses_536aad44bffe6L51dTRRIt5V5Q 1.0.119 2025-11 55 54 1 98.2%
ses_512df9ba2ffegCtDDsLlLexhHp 1.0.133 2025-12 59 52 8 88.1%
ses_4f80f8ad2fferlRmsYkOojNxkd 1.0.134 2025-12 60 52 7 86.7%
ses_4fe9e0b3affeHEWDCfwL66te8Y 1.0.134 2025-12 46 41 6 89.1%
ses_527529699ffekuNWoXDWa04JJS 1.0.123 2025-12 42 40 2 95.2%
ses_527155f95ffeuGkb5mK8cwyu6z 1.0.123 2025-12 40 38 3 95.0%
ses_512f23b10ffeQ7ZSR63YUt0ilo 1.0.133 2025-12 38 34 4 89.5%
ses_511f2166fffez21d76ueDDZPNK 1.0.133 2025-12 34 33 1 97.1%
ses_5367e3a7dffeVYzvyH3VG91TmC 1.0.119 2025-11 33 32 1 97.0%
ses_51e2cd440ffe4vCBpu1X3iRvZp 1.0.126 2025-12 33 32 1 97.0%
ses_51e0ce28affeuS6JBDHeYAO269 1.0.126 2025-12 31 30 1 96.8%
ses_4fef99f49ffe7AzGszWQLaA17z 1.0.134 2025-12 33 29 4 87.9%
ses_5409fc3f9ffeBObfuE4ePlsIu4 1.0.114 2025-11 28 27 2 96.4%
ses_52702c520ffejNachXvsjNQwTb 1.0.123 2025-12 28 27 1 96.4%
ses_5369daf51ffeOyhSD8Hg3mavh5 1.0.119 2025-11 27 26 1 96.3%
ses_54225fe3effeBTeLR9EikpM3WO 1.0.114 2025-11 25 24 1 96.0%
ses_53b8a25dcffeyiYprcalkBapZR 1.0.114 2025-11 24 23 1 95.8%
ses_5272e99c0ffeBcS3WvUv48zO9f 1.0.123 2025-12 24 23 1 95.8%
ses_4c9678c39ffeZ4WL3BQUPK7L7i 1.0.152 2025-12 26 22 4 84.6%
ses_4feced283ffey4090hEGlEVHLb 1.0.134 2025-12 33 21 11 63.6%
ses_52732aa4fffet0I4gKZNaruIam 1.0.123 2025-12 21 20 1 95.2%
ses_51c97696affewYnQwlntmI9x3b 1.0.128 2025-12 20 18 2 90.0%
ses_502742f7cffep4GWrZk50g3D8h 1.0.134 2025-12 14 13 1 92.9%
ses_4d99cad35ffe2ukM9YFBRctS1N 1.0.152 2025-12 18 10 9 55.6%
ses_5273b0850ffexoa5d9eqyqGsT0 1.0.123 2025-12 7 6 1 85.7%
ses_53cfbe0efffe15tvaozpS6Hd47 1.0.114 2025-11 5 4 1 80.0%
ses_4d9ac2cc0ffebN5EmySy2Z9y7n 1.0.152 2025-12 6 4 4 66.7%
ses_546051382ffet6DlhWKwa1j8Kj 1.0.110 2025-11 4 3 1 75.0%
ses_540a1ac3fffeBFaumwXu6geKfn 1.0.114 2025-11 5 3 4 60.0%
ses_4f8c9cc74ffe9h7ULToonrE2J7 1.0.134 2025-12 4 3 2 75.0%
ses_4fecfb533ffezITjGHIXG42rdU 1.0.134 2025-12 3 2 1 66.7%
ses_4fe425edaffemCF9sPEN9HGwju 1.0.134 2025-12 3 2 1 66.7%
ses_536d64aceffeJjBs33FJzP22JF 1.0.119 2025-11 2 1 1 50.0%
ses_536b9639bffeYpiwvFKLF2BviZ 1.0.119 2025-11 2 1 1 50.0%
ses_536ab017affeAyV6AkmI2P4LSi 1.0.119 2025-11 2 1 1 50.0%
ses_5367e960effeK10KUWDikCM0yy 1.0.119 2025-11 2 1 1 50.0%
ses_5363a0a65ffem7ObXEnhcLPwGS 1.0.119 2025-11 2 1 1 50.0%
ses_5273ccdfdffeh2OLkZzK7TYzhE 1.0.123 2025-12 2 1 1 50.0%
ses_527350ce9ffeKAoLlJZ3oImHag 1.0.123 2025-12 2 1 1 50.0%
ses_51b827cc3ffemmIc6euH8bGIVe 1.0.129 2025-12 2 1 2 50.0%

The ~18 "passed" messages in a 96%-loss session are the user rows (user messages don't require agent), which is why the sessions still appear in the list but are essentially empty.

Data is not destroyed

  • Legacy V1 tables are untouched: message = 198,228 rows (1.43 GB), part = 818,341 rows (1.79 GB) — including every skipped row.
  • kv['migration.v1-v2'] = {"phase":"completed"} — the migration will not re-run, and there is no documented repair path to re-project an individual session.

Environment

  • OpenCode: v2.0.19 (official binary)
  • OS: Fedora 44, kernel 7.2.7, x86_64
  • V1 history: sessions from v1.0.107 (2025-11) through v1.18.32 (2026-09)
  • DB: ~/.local/share/opencode/opencode.db (25.8 GB)

Suggestion

  1. Make agent optional (or default it) when decoding legacy V1 assistant messages — or backfill it from the session row during migration. The same latent problem exists for any other field V1 added after 1.0.x that is required today; a per-field tolerance with a warning would be safer than a hard skip.
  2. Provide a repair/re-run path: allow re-migrating a single session (e.g. clear its projection + bump the cursor) so data like this is recoverable without hand-editing SQLite.

Happy to share the full skip-log extract or additional samples.

Activity

  1. github-actions commented on Sep 30, 2026

    @github-actions
    Contributor

    This issue doesn't fully meet our contributing guidelines.

    What needs to be fixed:

    • Please use the Bug Report template and provide the required Description field; the current body does not follow an issue template.
    • Condense the exceptionally long report, especially the full 77-session table, into a concise description and reproduction context. Link the full data separately if needed.

    Please edit this issue to address the above within 2 hours, or it will be automatically closed.


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

    • #29908: Legacy message/part rows missing required fields, including agent, cause session-loading failures.
    • #42671: V1→V2 migration reports history loss related to undecodable rows.

    If you believe this was flagged incorrectly, please let a maintainer know.

  2. hallelujah-shih commented on Sep 30, 2026

    @hallelujah-shih
    Author

    Follow-up with full verification results, one correction to my suggestion, and a recovery procedure I validated against my own database.

    Full verification (all 5,544 rows, not the 200-sample)

    • 5,544/5,544 skipped rows are assistant messages missing exactly one field: agent. No other required field is missing anywhere. (15 rows additionally carry summary: true, which is valid per the Assistant schema — boolean, not the user-summary object.)
    • All 5,544 rows carry their own mode field: build ×5,393, plan ×149, general ×2.
    • The 3 invalid-part rows are all compaction markers with body {"type":"compaction"} (2 in ses_545a1fb14ffe2p4XUYntmcE1DW, 1 in ses_54603e939ffe9md4O51n41r5Ez).

    Correction to Suggestion #1

    The V1 session.agent column is NULL for 76 of the 77 affected sessions (only one has build), so it cannot serve as the backfill source. The correct source is the message's own mode field — in v1.0.x, mode is where the agent name was stored. Backfilling agent = mode makes all 5,544 rows pass SessionV1.Assistant validation with zero other changes (verified row-by-row above).

    Recovery procedure (validated on my DB, dry-run only so far)

    opencode service stop
    cp ~/.local/share/opencode/opencode.db ~/.local/share/opencode/opencode.db.bak  # 25.8 GB
    
    # 1. Backfill agent = mode on exactly the 5,544 skipped rows
    #    (id list derived from the "Skipped V1 migration row" log lines)
    python3 backfill.py            # UPDATE message SET data=json_set(data,'$.agent',mode)
    
    # 2. Reset migration state so the V1->V2 migration re-runs on next start
    sqlite3 ~/.local/share/opencode/opencode.db \
      "DELETE FROM kv WHERE key='migration.v1-v2';"
    
    opencode service start         # migration re-runs (cursor-based, resumable)

    Notes:

    • The migration iterates only the V1 session table, so V2-native sessions are untouched by a re-run. transformSession deletes each session's projection before re-inserting, so the re-run is idempotent for the other 2,902 sessions.
    • The 3 compaction parts will likely still be skipped (their minimal {"type":"compaction"} body doesn't satisfy the current CompactionPart schema); impact is cosmetic.
    • A per-session re-run mode (clear one session's projection + resume cursor at its id) would make this class of bug self-serviceable instead of requiring raw SQL.
  3. hallelujah-shih commented on Sep 30, 2026

    @hallelujah-shih
    Author

    Correction: true loss is 6,238 rows / 92 sessions — and the full root-cause chain is now sourced

    After deeper forensics (including reading the V1 source at the relevant tags), the numbers in the original report undercount the loss, and the root cause has a longer history than "v2 schema too strict". Corrected findings:

    1. True loss: 6,238 assistant rows across 92 sessions (not 5,544 / 77)

    The skip log itself is incomplete. Deriving the loss set from the DB instead of the log:

    SELECT count(*) FROM message
    WHERE json_extract(data,'$.role')='assistant'
      AND json_extract(data,'$.agent') IS NULL;
    -- = 6,238 rows, 92 sessions

    The log contains 5,544 of these 6,238 skip lines. 694 lines are missing from the log (15 sessions have zero skip lines despite 2–121 missing-agent rows each). All 5,547 present lines fall in one 2-second burst (2026-09-28T06:49:07.286Z → 06:49:09.147Z); the migration ran exactly once (single burst, no resume). So the WARN stream apparently dropped lines under load — the log is a lower bound, not an audit trail. (Secondary bug worth noting: the migration should emit a final summary with total skipped counts rather than relying on per-row WARNs.)

    All 6,238 rows are missing exactly one field (agent; 3 of them also lack optional finish). All 6,238 carry mode — the pre-rename name of the same field (distribution: build 5,845 / plan 386 / ddd-architect 5 / general 2).

    The mapping is provably a rename, not a guess: of all 177,699 assistant rows that do carry agent, 100% also carry mode with agent == mode (zero mismatches). Backfilling agent = mode reconstructs exactly what the writer would have produced.

    2. Full root-cause chain (verified against V1 source tags)

    1. Nov–Dec 2025 (V1 ≤ v1.0.152): assistant messages were persisted with mode only. packages/opencode/src/session/message-v2.ts at tag v1.0.152: Assistant has mode: z.string() and no agent.
    2. Between v1.0.152 and v1.0.202 (2025-12-22 → 12-26 in my DB): V1 added agent: z.string() (required) to Assistant and marked mode @deprecated — a rename, with no backfill of legacy rows. This was harmless inside V1 because the read path performs no runtime validation:
      // packages/opencode/src/session/message-v2.ts @ v1.3.0
      const info = (row) => ({ ...row.data, id: row.id, sessionID: row.session_id }) as MessageV2.Info
      Rows without agent load fine; consumers see agent === undefined and fall back.
    3. 2026-03-24 (v1.3.0): V1's storage engine switched from JSON files to SQLite. packages/opencode/src/storage/json-migration.ts imported all legacy JSON into the new tables as a faithful copy (11,413 messages / 45,961 parts / 336 todos in my DB; the uniform time_updated on those rows is the code's data.time?.updated ?? now fallback). Legacy rows still lacked agent — no repair was attempted, none was needed for V1.
    4. 2026-03-24 → 2026-09-28 (v1.3.x → v1.18.32): all newly written assistant messages carry agent. 91 of the 92 affected sessions were never touched again after 2025-12-25, so their legacy rows stayed in the old shape.
    5. 2026-09-28 (v2.0.19): the V1→V2 migration validates with Schema.decodeUnknownOption(SessionV1.Info), where SessionV1.Assistant declares agent required (packages/schema/src/v1/session.ts:465). Every legacy row fails → skipped with a per-row WARN.

    So: the field gap was born in V1 (required field introduced without backfill, tolerated by V1's unvalidated reads) and only became data loss when V2's migration made validation strict.

    3. Corrected recovery

    Backfill must be condition-based, not log-derived:

    -- the complete broken set (6,238 rows):
    UPDATE message
    SET data = json_set(data, '$.agent', json_extract(data, '$.mode'))
    WHERE json_extract(data, '$.role') = 'assistant'
      AND json_extract(data, '$.agent') IS NULL;

    then DELETE FROM kv WHERE key='migration.v1-v2'; and restart — the migration re-runs and re-projects everything. (V1 session.agent is NULL for 76/77 of the originally listed sessions, so the session row is not a usable backfill source; the message's own mode is — same field, pre-rename.)

    4. Residuals after recovery (by design, not loss)

    Even after backfill + re-run, some sessions' V2 projection will legitimately contain fewer rows than the V1 table, because transformSession collapses or drops certain shapes: compaction pairs (user + summary assistant → one compaction row), subagent task messages (parent has subtask part + task tool call), subtask-only user messages, and compaction user messages without a paired summary. None of these produce skip warnings; they are V2's intended projection semantics.

    The 3 {"type":"compaction"} parts from the V1.0.x era remain undecodable (V1.3+ CompactionPart requires auto: z.boolean()); their messages' compaction pairing is unaffected.

    All 92 affected sessions (corrected table)
    session v1 version created v1 msgs no-agent (lost) v2 proj loss
    ses_51e000dd4ffecZExS70p7re8TW 1.0.126 2025-12 465 447 17 96.1%
    ses_53cef287fffea6L8QL0Vh36lc0 1.0.114 2025-11 437 425 12 97.3%
    ses_545a1fb14ffe2p4XUYntmcE1DW 1.0.110 2025-11 321 311 10 96.9%
    ses_54a8a7505ffeFXnYtFntTE5MkM 1.0.107 2025-11 262 249 13 95.0%
    ses_536397eb6ffe7ppv4fRbJ52LKy 1.0.119 2025-11 219 213 6 97.3%
    ses_528071fb1ffeu5FLtJhvAhnsYy 1.0.123 2025-12 186 180 6 96.8%
    ses_5421ef5e4ffe3eNE14CQ9Ex1Zm 1.0.114 2025-11 795 172 621 21.6%
    ses_54603e939ffe9md4O51n41r5Ez 1.0.110 2025-11 178 172 7 96.6%
    ses_4da50572affeU1nGJbIQ7sZekp 1.0.152 2025-12 173 159 16 91.9%
    ses_540b71d94ffegyqnqonRHseB9Y 1.0.114 2025-11 165 159 9 96.4%
    ses_5351f23cbffe62TLi08jLFeOzH 1.0.119 2025-11 151 147 3 97.4%
    ses_5021187f5ffeZSi7MgERXPJYAi 1.0.134 2025-12 150 142 9 94.7%
    ses_51c106d0fffeI0Xzrp4Vejy1kS 1.0.126 2025-12 142 139 4 97.9%
    ses_51b8dcae3ffeCgRgl8vn4ZZyhy 1.0.129 2025-12 126 125 2 99.2%
    ses_536789327ffeJYZhvmrEuWBYws 1.0.119 2025-11 131 124 7 94.7%
    ses_51e276fe3ffes7GBQnryZjA1hO 1.0.126 2025-12 125 123 3 98.4%
    ses_56b24c609ffeQa1sdByEA85qbg 1.0.71 2025-11 130 121 9 93.1%
    ses_53771461affe4CnPSNJbunUzdL 1.0.119 2025-11 127 118 7 92.9%
    ses_4fe4f09c9ffeuvfd1SGhMBrlqG 1.0.134 2025-12 141 113 33 80.1%
    ses_5a30dd6c7ffemSZZ16Z5TRt5aO 1.0.39 2025-11 119 102 0 85.7%
    ses_503f00988ffeoK3algD9u4lgVi 1.0.134 2025-12 103 100 3 97.1%
    ses_52722b547ffedVFTLSa3zciWQD 1.0.123 2025-12 102 98 4 96.1%
    ses_53545dcf3ffewWY8BRiZGmjcp9 1.0.119 2025-11 100 95 5 95.0%
    ses_502333976ffe9pqlkjYcy3ksgi 1.0.134 2025-12 99 94 5 94.9%
    ses_536b90f61ffeWJLtkV3XMGwWcQ 1.0.119 2025-11 88 87 1 98.9%
    ses_52729b716ffeQSBGAZOd7MlZdu 1.0.123 2025-12 89 86 3 96.6%
    ses_537989628ffe11jEpqeUNJEI0R 1.0.119 2025-11 82 79 4 96.3%
    ses_4db01d874ffexK87fk2rt3bJ2V 1.0.152 2025-12 89 78 15 87.6%
    ses_5422ef80dffeV1tsdRbnaLN99I 1.0.110 2025-11 77 75 2 97.4%
    ses_5407e955affe9cJllWRRfPndLu 1.0.114 2025-11 77 74 3 96.1%
    ses_54089c199ffef8Gc0xih7kdj2W 1.0.114 2025-11 71 69 2 97.2%
    ses_53692e872ffe35A6pzOMeCSflC 1.0.119 2025-11 70 68 2 97.1%
    ses_540505ba1ffeli7Xa1h5J7wtvD 1.0.114 2025-11 69 66 3 95.7%
    ses_536c818e0ffezFb2wgJElolpsG 1.0.119 2025-11 67 64 3 95.5%
    ses_5403d1ce8ffeB9j9WDjjEH7pE7 1.0.114 2025-11 66 62 4 93.9%
    ses_5a3a81d07ffeZz1n9OxKtx7HHf 1.0.39 2025-11 66 61 0 92.4%
    ses_535a63703ffeKhjDUy0Lkr6XbO 1.0.119 2025-11 60 59 1 98.3%
    ses_51cf532e6ffekHNcXBPa6lIfjG 1.0.128 2025-12 63 58 6 92.1%
    ses_50381f9e2ffenWzH7pXjC7pt7H 1.0.134 2025-12 59 57 2 96.6%
    ses_503ff21f4ffeamKQcBUwruuHDB 1.0.134 2025-12 58 55 3 94.8%
    ses_536aad44bffe6L51dTRRIt5V5Q 1.0.119 2025-11 55 54 1 98.2%
    ses_56af1ef32ffeC9WIOD1wIze29K 1.0.71 2025-11 56 54 2 96.4%
    ses_512df9ba2ffegCtDDsLlLexhHp 1.0.133 2025-12 59 52 8 88.1%
    ses_4f80f8ad2fferlRmsYkOojNxkd 1.0.134 2025-12 60 52 7 86.7%
    ses_5a3c157d6ffeCNe3uG2Vy0SM7r 1.0.39 2025-11 58 50 0 86.2%
    ses_4fe9e0b3affeHEWDCfwL66te8Y 1.0.134 2025-12 46 41 6 89.1%
    ses_527529699ffekuNWoXDWa04JJS 1.0.123 2025-12 42 40 2 95.2%
    ses_527155f95ffeuGkb5mK8cwyu6z 1.0.123 2025-12 40 38 3 95.0%
    ses_54b2554a9ffeRnqyOpwEb6NcS8 1.0.106 2025-11 38 36 2 94.7%
    ses_512f23b10ffeQ7ZSR63YUt0ilo 1.0.133 2025-12 38 34 4 89.5%
    ses_511f2166fffez21d76ueDDZPNK 1.0.133 2025-12 34 33 1 97.1%
    ses_51e2cd440ffe4vCBpu1X3iRvZp 1.0.126 2025-12 33 32 1 97.0%
    ses_5367e3a7dffeVYzvyH3VG91TmC 1.0.119 2025-11 33 32 1 97.0%
    ses_51e0ce28affeuS6JBDHeYAO269 1.0.126 2025-12 31 30 1 96.8%
    ses_4fef99f49ffe7AzGszWQLaA17z 1.0.134 2025-12 33 29 4 87.9%
    ses_5a3d9f448ffeOi61t5PJh96k28 1.0.39 2025-11 31 28 0 90.3%
    ses_52702c520ffejNachXvsjNQwTb 1.0.123 2025-12 28 27 1 96.4%
    ses_5409fc3f9ffeBObfuE4ePlsIu4 1.0.114 2025-11 28 27 2 96.4%
    ses_5a3f5c8bdffe1wB8c6RjzPSEBx 1.0.39 2025-11 30 26 0 86.7%
    ses_5369daf51ffeOyhSD8Hg3mavh5 1.0.119 2025-11 27 26 1 96.3%
    ses_54225fe3effeBTeLR9EikpM3WO 1.0.114 2025-11 25 24 1 96.0%
    ses_5272e99c0ffeBcS3WvUv48zO9f 1.0.123 2025-12 24 23 1 95.8%
    ses_53b8a25dcffeyiYprcalkBapZR 1.0.114 2025-11 24 23 1 95.8%
    ses_4c9678c39ffeZ4WL3BQUPK7L7i 1.0.152 2025-12 26 22 4 84.6%
    ses_4feced283ffey4090hEGlEVHLb 1.0.134 2025-12 33 21 11 63.6%
    ses_52732aa4fffet0I4gKZNaruIam 1.0.123 2025-12 21 20 1 95.2%
    ses_51c97696affewYnQwlntmI9x3b 1.0.128 2025-12 20 18 2 90.0%
    ses_5a3ff5e69ffeZq16IsXw5qblKP 1.0.39 2025-11 18 17 0 94.4%
    ses_57f062d2dffeWfc61jqx1nqL4X 1.0.65 2025-11 19 17 0 89.5%
    ses_502742f7cffep4GWrZk50g3D8h 1.0.134 2025-12 14 13 1 92.9%
    ses_4d99cad35ffe2ukM9YFBRctS1N 1.0.152 2025-12 18 10 9 55.6%
    ses_54a8b65ebffemKv54IgTdkR7xF 1.0.107 2025-11 10 9 1 90.0%
    ses_5a273fb42ffeDR3MH7jkft4Gav 1.0.39 2025-11 13 7 0 53.8%
    ses_5273b0850ffexoa5d9eqyqGsT0 1.0.123 2025-12 7 6 1 85.7%
    ses_5a38b209bffeunjXACA1P7rwwe 1.0.39 2025-11 6 5 0 83.3%
    ses_5a3f5661bffeAFFiBHY7k8oQSP 1.0.39 2025-11 6 5 0 83.3%
    ses_4d9ac2cc0ffebN5EmySy2Z9y7n 1.0.152 2025-12 6 4 4 66.7%
    ses_53cfbe0efffe15tvaozpS6Hd47 1.0.114 2025-11 5 4 1 80.0%
    ses_546051382ffet6DlhWKwa1j8Kj 1.0.110 2025-11 4 3 1 75.0%
    ses_4f8c9cc74ffe9h7ULToonrE2J7 1.0.134 2025-12 4 3 2 75.0%
    ses_540a1ac3fffeBFaumwXu6geKfn 1.0.114 2025-11 5 3 4 60.0%
    ses_5a3c85067ffeghHZq1eN43BOiX 1.0.39 2025-11 4 2 0 50.0%
    ses_4fe425edaffemCF9sPEN9HGwju 1.0.134 2025-12 3 2 1 66.7%
    ses_4fecfb533ffezITjGHIXG42rdU 1.0.134 2025-12 3 2 1 66.7%
    ses_51b827cc3ffemmIc6euH8bGIVe 1.0.129 2025-12 2 1 2 50.0%
    ses_527350ce9ffeKAoLlJZ3oImHag 1.0.123 2025-12 2 1 1 50.0%
    ses_5273ccdfdffeh2OLkZzK7TYzhE 1.0.123 2025-12 2 1 1 50.0%
    ses_5363a0a65ffem7ObXEnhcLPwGS 1.0.119 2025-11 2 1 1 50.0%
    ses_5367e960effeK10KUWDikCM0yy 1.0.119 2025-11 2 1 1 50.0%
    ses_536ab017affeAyV6AkmI2P4LSi 1.0.119 2025-11 2 1 1 50.0%
    ses_536b9639bffeYpiwvFKLF2BviZ 1.0.119 2025-11 2 1 1 50.0%
    ses_536d64aceffeJjBs33FJzP22JF 1.0.119 2025-11 2 1 1 50.0%
  4. hallelujah-shih commented on Sep 30, 2026

    @hallelujah-shih
    Author

    Correction: true loss is 6,238 rows / 92 sessions — and the full root-cause chain is now sourced

    After deeper forensics (including reading the V1 source at the relevant tags), the numbers in the original report undercount the loss, and the root cause has a longer history than "v2 schema too strict". Corrected findings:

    1. True loss: 6,238 assistant rows across 92 sessions (not 5,544 / 77)

    The skip log itself is incomplete. Deriving the loss set from the DB instead of the log:

    SELECT count(*) FROM message
    WHERE json_extract(data,'$.role')='assistant'
      AND json_extract(data,'$.agent') IS NULL;
    -- = 6,238 rows, 92 sessions

    The log contains 5,544 of these 6,238 skip lines. 694 lines are missing from the log (15 sessions have zero skip lines despite 2–121 missing-agent rows each). All 5,547 present lines fall in one 2-second burst (2026-09-28T06:49:07.286Z → 06:49:09.147Z); the migration ran exactly once (single burst, no resume). So the WARN stream apparently dropped lines under load — the log is a lower bound, not an audit trail. (Secondary bug worth noting: the migration should emit a final summary with total skipped counts rather than relying on per-row WARNs.)

    All 6,238 rows are missing exactly one field (agent; 3 of them also lack optional finish). All 6,238 carry mode — the pre-rename name of the same field (distribution: build 5,845 / plan 386 / ddd-architect 5 / general 2).

    The mapping is provably a rename, not a guess: of all 177,699 assistant rows that do carry agent, 100% also carry mode with agent == mode (zero mismatches). Backfilling agent = mode reconstructs exactly what the writer would have produced.

    2. Full root-cause chain (verified against V1 source tags)

    1. Nov–Dec 2025 (V1 ≤ v1.0.152): assistant messages were persisted with mode only. packages/opencode/src/session/message-v2.ts at tag v1.0.152: Assistant has mode: z.string() and no agent.
    2. Between v1.0.152 and v1.0.202 (2025-12-22 → 12-26 in my DB): V1 added agent: z.string() (required) to Assistant and marked mode @deprecated — a rename, with no backfill of legacy rows. This was harmless inside V1 because the read path performs no runtime validation:
      // packages/opencode/src/session/message-v2.ts @ v1.3.0
      const info = (row) => ({ ...row.data, id: row.id, sessionID: row.session_id }) as MessageV2.Info
      Rows without agent load fine; consumers see agent === undefined and fall back.
    3. 2026-03-24 (v1.3.0): V1's storage engine switched from JSON files to SQLite. packages/opencode/src/storage/json-migration.ts imported all legacy JSON into the new tables as a faithful copy (11,413 messages / 45,961 parts / 336 todos in my DB; the uniform time_updated on those rows is the code's data.time?.updated ?? now fallback). Legacy rows still lacked agent — no repair was attempted, none was needed for V1.
    4. 2026-03-24 → 2026-09-28 (v1.3.x → v1.18.32): all newly written assistant messages carry agent. 91 of the 92 affected sessions were never touched again after 2025-12-25, so their legacy rows stayed in the old shape.
    5. 2026-09-28 (v2.0.19): the V1→V2 migration validates with Schema.decodeUnknownOption(SessionV1.Info), where SessionV1.Assistant declares agent required (packages/schema/src/v1/session.ts:465). Every legacy row fails → skipped with a per-row WARN.

    So: the field gap was born in V1 (required field introduced without backfill, tolerated by V1's unvalidated reads) and only became data loss when V2's migration made validation strict.

    3. Corrected recovery

    Backfill must be condition-based, not log-derived:

    -- the complete broken set (6,238 rows):
    UPDATE message
    SET data = json_set(data, '$.agent', json_extract(data, '$.mode'))
    WHERE json_extract(data, '$.role') = 'assistant'
      AND json_extract(data, '$.agent') IS NULL;

    then DELETE FROM kv WHERE key='migration.v1-v2'; and restart — the migration re-runs and re-projects everything. (V1 session.agent is NULL for 76/77 of the originally listed sessions, so the session row is not a usable backfill source; the message's own mode is — same field, pre-rename.)

    4. Second bug: re-running the migration destroys V2-appended history

    While validating the recovery on a full DB copy, I found that the per-session projection is an unconditional delete + re-insert of session_message (v1-migration.bun.ts: tx.delete(SessionMessageTable).where(eq(SessionMessageTable.session_id, next.id))). The migration iterates every V1 session row, so if a V1-origin session was resumed in V2 after the original migration, all messages V2 appended to it (user/assistant turns, compaction, idle markers) are wiped on any re-run. In my DB: 2,795 V1-origin sessions carry 3,635 rows whose ids are not in the V1 message table; 3,202 of those are projection artifacts (regenerated, harmless), but 433 rows in 3 sessions are genuine V2 conversation history that a re-run would silently destroy. Mitigation used in my recovery: temporarily move the affected sessions' V1 rows out of the session table for the re-run duration, then restore them (V2 reads only session_message/session_v2, so this is lossless).

    5. Recovery validated on a full DB copy

    Backfill + reset + re-run performed on a 25 GB .backup copy (isolated XDG_DATA_HOME, serve --port 49375): all 6,238 rows backfilled (0 skipped), migration re-ran in ~2 min, 91 of the 92 affected sessions fully recovered (V2 projection grew 968 → 7,285 rows; the one remaining 1-row gap is compaction-pair collapse, by design), zero new skip warnings, all other 2,887 sessions' projections byte-identical before/after.

    6. Residuals after recovery (by design, not loss)

    Even after backfill + re-run, some sessions' V2 projection will legitimately contain fewer rows than the V1 table, because transformSession collapses or drops certain shapes: compaction pairs (user + summary assistant → one compaction row), subagent task messages (parent has subtask part + task tool call), subtask-only user messages, and compaction user messages without a paired summary. None of these produce skip warnings; they are V2's intended projection semantics.

    The 3 {"type":"compaction"} parts from the V1.0.x era remain undecodable (V1.3+ CompactionPart requires auto: z.boolean()); their messages' compaction pairing is unaffected.

    All 92 affected sessions (corrected table)
    session v1 version created v1 msgs no-agent (lost) v2 proj loss
    ses_51e000dd4ffecZExS70p7re8TW 1.0.126 2025-12 465 447 17 96.1%
    ses_53cef287fffea6L8QL0Vh36lc0 1.0.114 2025-11 437 425 12 97.3%
    ses_545a1fb14ffe2p4XUYntmcE1DW 1.0.110 2025-11 321 311 10 96.9%
    ses_54a8a7505ffeFXnYtFntTE5MkM 1.0.107 2025-11 262 249 13 95.0%
    ses_536397eb6ffe7ppv4fRbJ52LKy 1.0.119 2025-11 219 213 6 97.3%
    ses_528071fb1ffeu5FLtJhvAhnsYy 1.0.123 2025-12 186 180 6 96.8%
    ses_5421ef5e4ffe3eNE14CQ9Ex1Zm 1.0.114 2025-11 795 172 621 21.6%
    ses_54603e939ffe9md4O51n41r5Ez 1.0.110 2025-11 178 172 7 96.6%
    ses_4da50572affeU1nGJbIQ7sZekp 1.0.152 2025-12 173 159 16 91.9%
    ses_540b71d94ffegyqnqonRHseB9Y 1.0.114 2025-11 165 159 9 96.4%
    ses_5351f23cbffe62TLi08jLFeOzH 1.0.119 2025-11 151 147 3 97.4%
    ses_5021187f5ffeZSi7MgERXPJYAi 1.0.134 2025-12 150 142 9 94.7%
    ses_51c106d0fffeI0Xzrp4Vejy1kS 1.0.126 2025-12 142 139 4 97.9%
    ses_51b8dcae3ffeCgRgl8vn4ZZyhy 1.0.129 2025-12 126 125 2 99.2%
    ses_536789327ffeJYZhvmrEuWBYws 1.0.119 2025-11 131 124 7 94.7%
    ses_51e276fe3ffes7GBQnryZjA1hO 1.0.126 2025-12 125 123 3 98.4%
    ses_56b24c609ffeQa1sdByEA85qbg 1.0.71 2025-11 130 121 9 93.1%
    ses_53771461affe4CnPSNJbunUzdL 1.0.119 2025-11 127 118 7 92.9%
    ses_4fe4f09c9ffeuvfd1SGhMBrlqG 1.0.134 2025-12 141 113 33 80.1%
    ses_5a30dd6c7ffemSZZ16Z5TRt5aO 1.0.39 2025-11 119 102 0 85.7%
    ses_503f00988ffeoK3algD9u4lgVi 1.0.134 2025-12 103 100 3 97.1%
    ses_52722b547ffedVFTLSa3zciWQD 1.0.123 2025-12 102 98 4 96.1%
    ses_53545dcf3ffewWY8BRiZGmjcp9 1.0.119 2025-11 100 95 5 95.0%
    ses_502333976ffe9pqlkjYcy3ksgi 1.0.134 2025-12 99 94 5 94.9%
    ses_536b90f61ffeWJLtkV3XMGwWcQ 1.0.119 2025-11 88 87 1 98.9%
    ses_52729b716ffeQSBGAZOd7MlZdu 1.0.123 2025-12 89 86 3 96.6%
    ses_537989628ffe11jEpqeUNJEI0R 1.0.119 2025-11 82 79 4 96.3%
    ses_4db01d874ffexK87fk2rt3bJ2V 1.0.152 2025-12 89 78 15 87.6%
    ses_5422ef80dffeV1tsdRbnaLN99I 1.0.110 2025-11 77 75 2 97.4%
    ses_5407e955affe9cJllWRRfPndLu 1.0.114 2025-11 77 74 3 96.1%
    ses_54089c199ffef8Gc0xih7kdj2W 1.0.114 2025-11 71 69 2 97.2%
    ses_53692e872ffe35A6pzOMeCSflC 1.0.119 2025-11 70 68 2 97.1%
    ses_540505ba1ffeli7Xa1h5J7wtvD 1.0.114 2025-11 69 66 3 95.7%
    ses_536c818e0ffezFb2wgJElolpsG 1.0.119 2025-11 67 64 3 95.5%
    ses_5403d1ce8ffeB9j9WDjjEH7pE7 1.0.114 2025-11 66 62 4 93.9%
    ses_5a3a81d07ffeZz1n9OxKtx7HHf 1.0.39 2025-11 66 61 0 92.4%
    ses_535a63703ffeKhjDUy0Lkr6XbO 1.0.119 2025-11 60 59 1 98.3%
    ses_51cf532e6ffekHNcXBPa6lIfjG 1.0.128 2025-12 63 58 6 92.1%
    ses_50381f9e2ffenWzH7pXjC7pt7H 1.0.134 2025-12 59 57 2 96.6%
    ses_503ff21f4ffeamKQcBUwruuHDB 1.0.134 2025-12 58 55 3 94.8%
    ses_536aad44bffe6L51dTRRIt5V5Q 1.0.119 2025-11 55 54 1 98.2%
    ses_56af1ef32ffeC9WIOD1wIze29K 1.0.71 2025-11 56 54 2 96.4%
    ses_512df9ba2ffegCtDDsLlLexhHp 1.0.133 2025-12 59 52 8 88.1%
    ses_4f80f8ad2fferlRmsYkOojNxkd 1.0.134 2025-12 60 52 7 86.7%
    ses_5a3c157d6ffeCNe3uG2Vy0SM7r 1.0.39 2025-11 58 50 0 86.2%
    ses_4fe9e0b3affeHEWDCfwL66te8Y 1.0.134 2025-12 46 41 6 89.1%
    ses_527529699ffekuNWoXDWa04JJS 1.0.123 2025-12 42 40 2 95.2%
    ses_527155f95ffeuGkb5mK8cwyu6z 1.0.123 2025-12 40 38 3 95.0%
    ses_54b2554a9ffeRnqyOpwEb6NcS8 1.0.106 2025-11 38 36 2 94.7%
    ses_512f23b10ffeQ7ZSR63YUt0ilo 1.0.133 2025-12 38 34 4 89.5%
    ses_511f2166fffez21d76ueDDZPNK 1.0.133 2025-12 34 33 1 97.1%
    ses_51e2cd440ffe4vCBpu1X3iRvZp 1.0.126 2025-12 33 32 1 97.0%
    ses_5367e3a7dffeVYzvyH3VG91TmC 1.0.119 2025-11 33 32 1 97.0%
    ses_51e0ce28affeuS6JBDHeYAO269 1.0.126 2025-12 31 30 1 96.8%
    ses_4fef99f49ffe7AzGszWQLaA17z 1.0.134 2025-12 33 29 4 87.9%
    ses_5a3d9f448ffeOi61t5PJh96k28 1.0.39 2025-11 31 28 0 90.3%
    ses_52702c520ffejNachXvsjNQwTb 1.0.123 2025-12 28 27 1 96.4%
    ses_5409fc3f9ffeBObfuE4ePlsIu4 1.0.114 2025-11 28 27 2 96.4%
    ses_5a3f5c8bdffe1wB8c6RjzPSEBx 1.0.39 2025-11 30 26 0 86.7%
    ses_5369daf51ffeOyhSD8Hg3mavh5 1.0.119 2025-11 27 26 1 96.3%
    ses_54225fe3effeBTeLR9EikpM3WO 1.0.114 2025-11 25 24 1 96.0%
    ses_5272e99c0ffeBcS3WvUv48zO9f 1.0.123 2025-12 24 23 1 95.8%
    ses_53b8a25dcffeyiYprcalkBapZR 1.0.114 2025-11 24 23 1 95.8%
    ses_4c9678c39ffeZ4WL3BQUPK7L7i 1.0.152 2025-12 26 22 4 84.6%
    ses_4feced283ffey4090hEGlEVHLb 1.0.134 2025-12 33 21 11 63.6%
    ses_52732aa4fffet0I4gKZNaruIam 1.0.123 2025-12 21 20 1 95.2%
    ses_51c97696affewYnQwlntmI9x3b 1.0.128 2025-12 20 18 2 90.0%
    ses_5a3ff5e69ffeZq16IsXw5qblKP 1.0.39 2025-11 18 17 0 94.4%
    ses_57f062d2dffeWfc61jqx1nqL4X 1.0.65 2025-11 19 17 0 89.5%
    ses_502742f7cffep4GWrZk50g3D8h 1.0.134 2025-12 14 13 1 92.9%
    ses_4d99cad35ffe2ukM9YFBRctS1N 1.0.152 2025-12 18 10 9 55.6%
    ses_54a8b65ebffemKv54IgTdkR7xF 1.0.107 2025-11 10 9 1 90.0%
    ses_5a273fb42ffeDR3MH7jkft4Gav 1.0.39 2025-11 13 7 0 53.8%
    ses_5273b0850ffexoa5d9eqyqGsT0 1.0.123 2025-12 7 6 1 85.7%
    ses_5a38b209bffeunjXACA1P7rwwe 1.0.39 2025-11 6 5 0 83.3%
    ses_5a3f5661bffeAFFiBHY7k8oQSP 1.0.39 2025-11 6 5 0 83.3%
    ses_4d9ac2cc0ffebN5EmySy2Z9y7n 1.0.152 2025-12 6 4 4 66.7%
    ses_53cfbe0efffe15tvaozpS6Hd47 1.0.114 2025-11 5 4 1 80.0%
    ses_546051382ffet6DlhWKwa1j8Kj 1.0.110 2025-11 4 3 1 75.0%
    ses_4f8c9cc74ffe9h7ULToonrE2J7 1.0.134 2025-12 4 3 2 75.0%
    ses_540a1ac3fffeBFaumwXu6geKfn 1.0.114 2025-11 5 3 4 60.0%
    ses_5a3c85067ffeghHZq1eN43BOiX 1.0.39 2025-11 4 2 0 50.0%
    ses_4fe425edaffemCF9sPEN9HGwju 1.0.134 2025-12 3 2 1 66.7%
    ses_4fecfb533ffezITjGHIXG42rdU 1.0.134 2025-12 3 2 1 66.7%
    ses_51b827cc3ffemmIc6euH8bGIVe 1.0.129 2025-12 2 1 2 50.0%
    ses_527350ce9ffeKAoLlJZ3oImHag 1.0.123 2025-12 2 1 1 50.0%
    ses_5273ccdfdffeh2OLkZzK7TYzhE 1.0.123 2025-12 2 1 1 50.0%
    ses_5363a0a65ffem7ObXEnhcLPwGS 1.0.119 2025-11 2 1 1 50.0%
    ses_5367e960effeK10KUWDikCM0yy 1.0.119 2025-11 2 1 1 50.0%
    ses_536ab017affeAyV6AkmI2P4LSi 1.0.119 2025-11 2 1 1 50.0%
    ses_536b9639bffeYpiwvFKLF2BviZ 1.0.119 2025-11 2 1 1 50.0%
    ses_536d64aceffeJjBs33FJzP22JF 1.0.119 2025-11 2 1 1 50.0%
  5. github-actions commented on Sep 30, 2026

    @github-actions
    Contributor

    This issue has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window.

    Feel free to open a new issue that follows our issue templates.

  6. hallelujah-shih commented on Sep 30, 2026

    @hallelujah-shih
    Author

    Production recovery complete — runbook, results, operational notes

    The recovery has now been executed on the live database (v2.0.20, 25.8 GB). Full procedure, in order:

    1. opencode service stop — wait until the process has actually exited (note 1 below!)
    2. Backup: cp opencode.db opencode.db.pre-repair (plus -wal if non-empty)
    3. Protect sessions that were resumed under V2 (see "second bug" above). Identify V1-origin sessions whose session_message contains rows with ids absent from the V1 message table and post-migration timestamps (in my DB: 3 sessions, 433 rows of genuine V2 conversation history). Temporarily move their V1 rows aside so the re-run skips them:
      CREATE TABLE _bak_session AS SELECT * FROM session WHERE id IN (<ids>);
      CREATE TABLE _bak_message AS SELECT * FROM message WHERE session_id IN (<ids>);
      CREATE TABLE _bak_part    AS SELECT * FROM part    WHERE session_id IN (<ids>);
      DELETE FROM message WHERE session_id IN (<ids>);
      DELETE FROM part    WHERE session_id IN (<ids>);
      DELETE FROM session WHERE id IN (<ids>);
      (V2 reads only session_v2/session_message, so this is lossless; rows come back in step 9.)
    4. Condition-based backfill:
      UPDATE message
      SET data = json_set(data, '$.agent', json_extract(data, '$.mode'))
      WHERE json_extract(data, '$.role') = 'assistant'
        AND json_extract(data, '$.agent') IS NULL;
    5. DELETE FROM kv WHERE key = 'migration.v1-v2';
    6. opencode service start — migration re-runs on boot (~2 min for 2,979 sessions)
    7. Poll kv.migration.v1-v2 until {"phase":"completed"}
    8. Verify: per-session V2 projection grew by the backfilled count (minus by-design compaction/subtask folding); protected sessions' row counts unchanged; no new skip warnings
    9. Restore the protected V1 rows (INSERT ... SELECT from the _bak_* tables, verify counts, drop the tables)
    10. Optional, in a later stop window: VACUUM; PRAGMA wal_checkpoint(TRUNCATE); — the migration's mass DELETE had left a 19.6 GB freelist; the file shrank 25.8 GB → 6.2 GB.

    Results

    • 6,238 / 6,238 rows backfilled (0 rows lacked mode)
    • 91 of 92 affected sessions fully recovered (V2 projection 968 → 7,285 rows); the single remaining 1-row gap in the last session is by-design compaction-pair folding, not loss
    • 3 protected sessions: all V2-appended history (433 rows) and V1 rows intact
    • All other 2,887 sessions' projections row-identical before/after the re-run
    • Zero agent-missing rows remain; session/message/part counts identical to baseline
    • opencode stats --all now reflects the recovered content (Nov–Dec 2025 activity restored)

    Operational notes (bites encountered on the live run)

    1. Graceful shutdown can take ~9 minutes. service stop sends SIGTERM, but the service waits for background fibers — I observed a models.dev fetch timing out at ~5 min plus MCP reconnection spans. A stop script that gives up after ~1 minute will misreport failure and must not touch the DB; poll for up to 12 min (SIGKILL is safe: SQLite WAL is crash-safe).
    2. The TUI auto-respawns the service ~10 s after a stop. An unattended repair script must (a) tolerate an already-running service at the start step (controlled restart) and (b) serialize itself (lockfile) — in my run a manually re-launched TUI respawned the service mid-repair and stole the stop; the cursor-based resume absorbed it cleanly.
    3. Cursor-based resume confirmed in production: a service killed mid-re-run resumed from the saved cursor on next boot, with no duplication or corruption. The re-run is safe to interrupt.
    4. The per-row WARN-only logging (mentioned earlier) is what turned this into a multi-day forensic investigation instead of a one-line diagnosis — a final summary with total skipped counts at the end of the migration would have surfaced it immediately.
  7. changed the title [-]V1→V2 migration silently drops v1.0.x-era assistant messages: required `agent` field fails schema validation (77 sessions lost 91–99% of content)[/-] [+]V1→V2 migration silently drops v1.0.x-era assistant messages: required `agent` field fails schema validation — 6,238 messages across 92 sessions[/+] on Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

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