[Bug]: Desktop typing freezes 1.5–2 s every ~30 s while storage.saveThread re-encodes a large thread snapshot on the main thread#29417
What happens
On the Linux desktop app, typing stops appearing for 1.5–2 seconds about every 20–30 seconds. The window's renderer main thread is busy for that whole time. The freezes continue while the user is viewing a different, small thread.
Version: 0.1.7-nightly.20261102.4080 (AppImage, Linux, Wayland). The same memory churn in the renderer was present on the previous nightly.
Cause
Each freeze is one storage.saveThread call for a large remote thread that the renderer still holds. The user was not viewing that thread. Its encoded cache value is about 47 MB. saveThread encodes the whole OrchestrationV2ThreadDetailSnapshot with Effect Schema into one JSON string and writes it to IndexedDB. The work runs on the main thread in one step and never yields.
Upstream source (main at ac8dce0c):
packages/client-runtime/src/state/threads.ts:EnvironmentThreadState.persist, queued fromqueuePersist(...)after a run settles and again in the settled-items pass (settleTurnItems→queuePersist({ ...saved, projection })).apps/web/src/connection/storage.ts:storage.saveThread→encodeStoredThreadSnapshot(...)→writeDatabaseValue(...).
The comment above the first queuePersist says saves wait until the run settles "so cache encoding stays off the streaming path". A thread that runs many short agent turns settles every 30–50 s. So in practice it re-encodes the full snapshot each time, and each settle produces two saves about 11.6 s apart.
Evidence
- Renderer CPU profile (CDP
Profileron the main webContents, 60 s, 0.5 ms sampling). There were two busy runs of 1.6 s, at 10:35:46.7 and 10:35:58.4. Both have the same profile:- ~97% of samples sit under one Effect fiber.
- Self time: 41%
JSON.stringify(the SchemafromJsonStringencodetry), 20% Schema structonItem, 15% nativeIDBObjectStore.put, 7% Schemaencode. - Only ~2% is GC.
- Client spans forwarded to the server trace:
storage.saveThread/EnvironmentThreadState.persiststarted at 10:35:46.717 (1648 ms) and 10:35:58.381 (1702 ms). Each matches a profiled busy run within 30 ms. - Over ~20 minutes, 34
saveThreadspans took 1.5–2.7 s each. They came in pairs about 11.6 s apart, with a new pair every 30–50 s. Every typing freeze we timed (via/procper-thread CPU at 250 ms) falls on one of them. - IndexedDB: a 47 MB value appeared in
t3code_app_0.indexeddb.blobone second after each of those saves. Its header showsschemaVersion: 5, a remoteenvironmentId, and asnapshotSequencethat rose by 1 between the two saves of a pair. That thread has ~1,860 snapshot sequences and a long tool history. - The renderer working set swings by 300–400 MB per minute (e.g. 572 → 931 MB between two 1-minute
renderer-history.ndjsonsamples). Peak is 1.6 GB.
Suggested fixes
Any one of these would remove the freeze:
- Skip or cap the thread cache for very large snapshots. For example, persist only the bounded tail window that a cold start needs, not the full loaded history.
- Do the encode and write off the main thread (a worker or structured clone of a plain object), or in chunks that yield to input.
- Throttle
persistper thread: at most one save per N minutes while the thread keeps settling, plus a final save on unmount. - Persist only threads the user is viewing. A thread kept alive by the resume cache doesn't need a save after every settle.
Related, not duplicates: #9661 (W14: IndexedDB caches have no size or age eviction) and #22985 (renderer freeze from reasoning previews).