Problem
SESSION_STATUSES still carries review and done. Nothing in current source writes either, so they are dead as far as the runtime is concerned — but they cannot simply be removed, and #3033 reverted an attempt to do exactly that.
Why removal is a migration
resolveLegacyStatus in the JSONL→SQLite importer did if (isSessionStatus(header.status) && header.status !== 'archived') return header.status;. At that time isSessionStatus accepted both values, so a legacy header carrying done or review was written into a real user's SQLite store verbatim. That importer only went away in #2656 (90bdb3093).
The failure is not graceful. normalizeSessionHeader (packages/storage/src/session-store.ts) validates status inside a conjunction and throws Invalid session header for session <id>: malformed fields for the WHOLE header, and the catalog reads a page at a time (sqlite-session-metadata-store.ts → decodeCatalogRecord). One stored row carrying done therefore fails an entire catalog page, not just its own row — the task list does not open. The wire decoders in packages/runtime-host/src/protocol reject the value the same way.
The SQLite schema is still version 24 and status is an unconstrained TEXT column, so nothing has ever rewritten those rows.
Options
- Schema migration to v25 rewriting
done/review to active. Most thorough; needs the usual migration review.
- Tolerant read: map an unrecognised status to
active in normalizeSessionHeader instead of throwing. Smaller, and it also makes any future narrowing safe, but it changes what "malformed" means at that boundary.
Either way the protocol side needs a compatibility window if Host and Desktop can be at different versions.
Context
Found while adjudicating the adversarial review of #3033; Codex reproduced the catalog failure by writing status=done into a real store and reopening it.
Problem
SESSION_STATUSESstill carriesreviewanddone. Nothing in current source writes either, so they are dead as far as the runtime is concerned — but they cannot simply be removed, and #3033 reverted an attempt to do exactly that.Why removal is a migration
resolveLegacyStatusin the JSONL→SQLite importer didif (isSessionStatus(header.status) && header.status !== 'archived') return header.status;. At that timeisSessionStatusaccepted both values, so a legacy header carryingdoneorreviewwas written into a real user's SQLite store verbatim. That importer only went away in #2656 (90bdb3093).The failure is not graceful.
normalizeSessionHeader(packages/storage/src/session-store.ts) validatesstatusinside a conjunction and throwsInvalid session header for session <id>: malformed fieldsfor the WHOLE header, and the catalog reads a page at a time (sqlite-session-metadata-store.ts→decodeCatalogRecord). One stored row carryingdonetherefore fails an entire catalog page, not just its own row — the task list does not open. The wire decoders inpackages/runtime-host/src/protocolreject the value the same way.The SQLite schema is still version 24 and
statusis an unconstrainedTEXTcolumn, so nothing has ever rewritten those rows.Options
done/reviewtoactive. Most thorough; needs the usual migration review.activeinnormalizeSessionHeaderinstead of throwing. Smaller, and it also makes any future narrowing safe, but it changes what "malformed" means at that boundary.Either way the protocol side needs a compatibility window if Host and Desktop can be at different versions.
Context
Found while adjudicating the adversarial review of #3033; Codex reproduced the catalog failure by writing
status=doneinto a real store and reopening it.