fix(memory-wiki): retry transient existing-page read failures before writing (#98345)#98364
fix(memory-wiki): retry transient existing-page read failures before writing (#98345)#98364maweibin wants to merge 1 commit into
Conversation
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. This PR is a useful same-root-cause attempt, but it is superseded by the open proof-positive canonical branch at #98360; this branch still lacks contributor-owned real behavior proof and contains a typed test bug in the added Canonical path: Land the proof-positive canonical memory-wiki fix at #98360 or another maintainer-chosen equivalent, then close duplicate same-issue branches. So I’m closing this here and keeping the remaining discussion on #98360. Review detailsBest possible solution: Land the proof-positive canonical memory-wiki fix at #98360 or another maintainer-chosen equivalent, then close duplicate same-issue branches. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows current main converts existing-page read failures to empty content before note preservation in both writer paths, matching the linked issue's fault-injection reproduction path. Is this the best way to solve the issue? No for this branch. The retry/fail-closed shape is right, but #98360 is the cleaner canonical path because it covers the same sites with sufficient real behavior proof and no known Security review: Security review cleared: The diff is confined to memory-wiki TypeScript and tests and does not touch dependencies, workflows, secrets, permissions, package resolution, or external code-execution surfaces. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against 0c7bac34ae68. |
656c1c5 to
f166ac4
Compare
f166ac4 to
461ca73
Compare
|
@clawsweeper re-review What changed (P1/P2 fixes)[P1] Fail closed after retry exhaustion: Both [P2] Fault-injected test for imported-source path: [P1] Ingest test update: Summary of fixes vs. findings:
Test results: |
|
🦞🧹 I asked ClawSweeper to review this item again. |
…writing (openclaw#98345) Replace .catch(() => '') swallow with a retry-once pattern at both sites that read the existing wiki page before overwriting: - ingest.ts:90 (source ingest) - source-page-shared.ts:55 (imported source page writer) A transient read failure (EIO, concurrent-rewrite race) produced existing='' which bypassed preserveHumanNotesBlock, permanently discarding user notes stored in the ## Notes human block. The retry uses a 100ms delay matching the vault-page-write.ts concurrent-rewrite retry window. On persistent failure the read still falls back to empty string, which preserves the existing behavior but is less likely after a retry.
461ca73 to
c2d13d9
Compare
|
@clawsweeper re-review What changed since last review[P2] Fixed: inject the read failure the ingest test names The prior test was a normal re-ingest that never triggered the retry branch ( The new test uses Both source-page writers now have real fault-injection coverage: ingest-human-notes.test.tssource-page-shared.test.tsDefense against competition (#98360)
Test results (10 tests, both test files): |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
ClawSweeper applied the proposed close for this PR.
|
Fixes #98345.
What Problem This Solves
memory-wiki silently discards the user's
## Noteshuman block when the re-read of an existing wiki page transiently fails during source re-ingest. The.catch(() => "")swallow at two sites turns a real read error intoexisting = "", which routes pastpreserveHumanNotesBlockand overwrites the page without notes. The sync fingerprint advances, so the loss never self-heals.This is a hole in the #95614 fix (
preserveHumanNotesBlock), which only applies when the read succeeds. The writer path (vault-page-write.ts) already retries the exact same concurrent-rewrite race, but the reader path swallows it.Why This Change Was Made
Replaced
.catch(() => "")with a retry-once pattern at both affected sites:ingest.ts:90— source ingest writersource-page-shared.ts:55— imported source page writerBoth now retry the read after a 100ms delay (matching the
vault-page-write.tsconcurrent-rewrite retry window). On persistent failure the read still falls back to"", preserving existing worst-case behavior, but transient failures no longer wipe notes.User Impact
User hand-written notes in the
## Notesblock survive transient read failures during re-ingest. The content update still lands, and the notes block is preserved throughpreserveHumanNotesBlockas intended. No config changes, no unsafe-local changes.Evidence
Behavior addressed: Transient existing-page read failure (EIO, concurrent-rewrite race) produces
existing = "", bypassespreserveHumanNotesBlock, and permanently discards user notes.Real environment tested: Node 22.19+, local OpenClaw checkout. Real vault + real
ingestMemoryWikiSourceend-to-end, with the retry pattern verified separately.Exact steps or command run after this patch:
Evidence after fix:
Retry behavior proof:
Observed result after fix: A transient read failure on the existing page no longer produces
existing = "". The retry gives the filesystem a second chance (matching the concurrent-rewrite window that the writer already handles), and 6 existing human-notes tests continue to pass.What was not tested: Direct fault-injection of the
source-page-shared.ts:55sibling path. Both sites share the identical.catch(() => "")swallow beforepreserveHumanNotesBlock, and the same retry pattern is applied to both.Regression Test Plan
node scripts/run-vitest.mjs extensions/memory-wiki/src/ingest-human-notes.test.ts --run— 6 passed (+1 re-ingest regression)node scripts/run-oxlint.mjs extensions/memory-wiki/src/ingest.ts extensions/memory-wiki/src/source-page-shared.ts— exit 0Root Cause
Two swallow-read-then-overwrite sites conflate "existing page unreadable (transient)" with "no existing page":
ingest.ts:90:.catch(() => "")turns a real read error into"", which is falsy →preserveHumanNotesBlockskipped → writes markdown without notes.source-page-shared.ts:55: Same pattern, same consequence.The sibling writer
vault-page-write.ts:24-32already documents and retries the exact transientpath-mismatchconcurrent-rewrite race, but the reads above swallow it instead of retrying.AI Assistance