Repository navigation
pending-changes.json grows without bound — resolved proposals keep their full note text, parsed and rewritten whole on the main thread - #539
Conversation
|
…ws for the review chips
| for (const entry of this.#entries) { | ||
| if (entry.status === "pending") continue; | ||
| if (!entry.reportedToModel) continue; | ||
| if (this.hasUnrevertedApplication(entry)) continue; | ||
| const change = entry.change; | ||
| if (change.type === "update") { | ||
| if (change.originalContent === "" && change.newContent === "") continue; | ||
| change.originalContent = ""; | ||
| change.newContent = ""; | ||
| } else if (change.type === "delete") { | ||
| if (change.originalContent === "") continue; | ||
| change.originalContent = ""; | ||
| } else if (change.type === "create") { | ||
| if (change.content === "") continue; | ||
| change.content = ""; | ||
| } else { | ||
| continue; | ||
| } | ||
| changed = true; | ||
| } |
There was a problem hiding this comment.
Historical entries remain unbounded
Once an outcome is reported, this loop blanks its note text but keeps the entry forever. Every future load still parses and validates every historical entry, and every save serializes them all. Over time, that brings back growing main-thread work and file size, even though the old note text is gone. Consider a bounded status record that still lets chat cards show completed reviews.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/stores/pendingChangesStore.svelte.ts
Line: 301-320
Comment:
**Historical entries remain unbounded**
Once an outcome is reported, this loop blanks its note text but keeps the entry forever. Every future load still parses and validates every historical entry, and every save serializes them all. Over time, that brings back growing main-thread work and file size, even though the old note text is gone. Consider a bounded status record that still lets chat cards show completed reviews.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.|
Refreshed numbers from a live desktop vault (Obsidian 1.13.7, S2B 2.3.0, Windows), measured 2026-10-04. Flagging these rather than leave a stale figure standing:
The defect looks unchanged (append-only resolved entries, each keeping full If useful, I will re-measure before/after a prune on the current build and post the pair. |
Environment
ollama:nomic-embed-text:latest.smart-env, no live Ollama process)Summary
data/pending-changes.jsonis an append-only array holding every change S2B has ever proposed, each entry storing the fulloriginalContentand fullnewContentof the note. Nothing prunes entries once the user accepts or rejects them. After five days of ordinary use the file reached 50 MB across 393 entries, 94% of which were already resolved (308 accepted, 79 rejected, 6 pending).That dead history is then paid for twice:
PendingChanges.load()reads the whole file,JSON.parses it, and runs a ZodsafeParseover the entire array — all on the renderer's main thread.saveToDisk()doesJSON.stringify(entireArray, null, 2)— the whole array, pretty-printed — on a 1,000 ms debounce, and rewrites all 50 MB.The array also stays resident as reactive state, so the full 50 MB of strings is permanently live, with a ~50 MB transient allocation per save on top.
Evidence — the plugin's own instrumentation
From
data/startup-timings.json, same vault, same device, successive runs:pendingChanges:loadThe phase is flagged
"blocking": trueand is 98% of total startup in the last run. The JS heap sits at 2,497 MB against ajsHeapLimitMBof 4,192 before any work happens — so the renderer starts each session 60% of the way to an OOM crash.Host-level, during that session: Obsidian renderer RSS 3.50 GB, 28% CPU sustained, and 3,397 MB of 4,095 MB swap consumed — the machine as a whole becomes unresponsive, not just Obsidian.
Note the non-linearity between the last two rows: the file barely changed, but the load time went up ~60×. Once the renderer is swapping, the 50 MB parse-plus-validate degrades catastrophically, which makes this self-reinforcing rather than merely slow.
Reproduction
data/pending-changes.jsonkeeps every resolved entry with both full texts.pendingChanges:loadindata/startup-timings.json.Suggested fix
originalContent/newContentare dead weight. Keep the metadata (id, path, status, timestamps,reportedToModel) and delete the text. This alone would have kept the file under ~1 MB. Caveat: keying onstatusalone is unsafe — a partially-accepted entry keepsinitialOriginalContent, andoriginalContent/newContentare the only recordrevertAppliedGroupscan restore the note from; pruning those would silently destroy the user's undo.pendingentries from chat threads closed 1–3 days earlier; they can never be actioned). Note.chatcheckpoints were not capped in v2.1.0 — no cap exists there either; see the note at the top.JSON.stringify(x, null, 2)on a hot path inflates both the string allocation and the bytes written for a file no human reads.Workaround in use
Automation/s2b_prune_pending_changes.pyin our vault keepspendingplus any resolved-but-unreported entries and drops the rest: 393 → 49 entries, 47.8 MB → 4.6 MB (90% smaller).It has to refuse to run while Obsidian is open, because
saveToDisk()serialises from the in-memory array and never re-reads disk — so an on-disk prune is silently overwritten within ~1 s of the next change, and lost. Worth noting as a hazard for anyone else hand-editing this file.A fix branch exists:
Direct-Launch/smart-second-brain#fix/prune-pending-payload— prunes exactly as item 1 describes, at both the load and save boundaries, with tests (PR linked separately).