From 446e616df0b53f49fac6c883a07fad96be06df32 Mon Sep 17 00:00:00 2001 From: Adam Firestone Date: Wed, 12 Aug 2026 16:11:45 -0500 Subject: [PATCH] fix(web): invalidate thread snapshot caches predating migration 044 Migration 044 deleted orphaned retracted messages directly in sqlite, which emits no orchestration events - warm IndexedDB thread caches resume via afterSequence and would render the deleted ghost messages forever, surviving hard reloads. Bump the stored thread snapshot schema version so pre-044 cache records fail decode and clients refetch clean snapshots. Model: Claude Fable 5 Harness: Claude Code --- apps/web/src/connection/storage.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/web/src/connection/storage.ts b/apps/web/src/connection/storage.ts index 20315c8c1c82..bbe234eed0de 100644 --- a/apps/web/src/connection/storage.ts +++ b/apps/web/src/connection/storage.ts @@ -58,8 +58,12 @@ const StoredShellSnapshotJson = Schema.fromJsonString(StoredShellSnapshot); // v4 invalidates snapshots cached before the wire projection retained tool // output fields (result/aggregatedOutput); a warm cache resumes via // `afterSequence` and would otherwise show stripped payloads forever. +// v5 invalidates snapshots cached before server migration 044 deleted +// orphaned retracted messages out-of-band: the deletion emits no events, so +// a warm cache resuming via `afterSequence` would render the ghost messages +// forever. Any server-side row surgery needs a bump here to reach clients. const StoredThreadSnapshot = Schema.Struct({ - schemaVersion: Schema.Literal(4), + schemaVersion: Schema.Literal(5), environmentId: EnvironmentId, threadId: ThreadId, snapshot: OrchestrationThreadDetailSnapshot, @@ -567,7 +571,7 @@ export const connectionStorageLayer = Layer.effectContext( saveThread: (environmentId, snapshot) => Effect.gen(function* () { const encoded = yield* encodeStoredThreadSnapshot({ - schemaVersion: 4, + schemaVersion: 5, environmentId, threadId: snapshot.thread.id, snapshot,