session-catchup.py OpenCode adapter raises on malformed part rows instead of skipping them
Problem
The OpenCode adapter in session-catchup.py assumes well-formed rows in OpenCode's SQLite store. Two malformed shapes make the whole catchup run raise instead of skipping the row:
_format_opencode_partdoesstate = data.get('state') or {}and thenstate.get('input'). When a part row carriesstateas a string or list (not a dict), this raisesAttributeError.- The SQL query uses
json_extract(data, '$.type')on every part row. When a row'sdatacolumn is not valid JSON, SQLite raisesmalformed JSONfor the whole statement, so nothing from that session is summarised.
Scope: the explicit session-catchup.py command on projects that use OpenCode, and only with corrupted or hand-edited OpenCode rows. Normal OpenCode stores are well-formed and are not affected. The Claude Code, Codex, Hermes and Pi catchup paths are separate code and are not affected.
Copies: the same code exists in the root scripts/session-catchup.py (Claude Code plugin path), skills/planning-with-files/scripts/session-catchup.py, the five skills/i18n/*/scripts/ copies, and every host bundle synced from the canonical file (python scripts/sync-ide-folders.py --verify lists them). The fix lands in the canonical copy and is synced from there; the root copy and the i18n copies are not covered by the sync tool and are updated by hand.
How to reproduce
Point the adapter at a copy of an OpenCode store, then:
UPDATE part SET data = '{"type":"tool","tool":"write","state":"broken"}' WHERE id = '<some part id>';and run python scripts/session-catchup.py <project>: AttributeError: 'str' object has no attribute 'get'.
Then:
UPDATE part SET data = 'not json' WHERE id = '<some part id>';and run it again: sqlite3.OperationalError: malformed JSON.
Suggested fix
- Guard the shape:
input_ = state.get('input') if isinstance(state, dict) else None(the same guard the read path a few lines below already uses), and treat a non-dictinput_as empty. - Filter non-JSON rows in SQL with
json_valid(data)in theWHEREclause (available in SQLite 3.9+, which every supported Python ships), or catchsqlite3.OperationalErrorper session and skip that session with a one-line notice on stderr. - Add two rows of this shape to the existing OpenCode fixture in
tests/and assert the run completes and reports the healthy rows.
Origin
Found by the v3.19.0 release review (PRs #247, #248, #249). Pre-existing behaviour. Tracked as item 6 of #250 before that issue was split into one issue per item.
Source: OthmanAdi/planning-with-files