remove_recovery_artifacts (engine/crates/lex-core/src/user_history/recovery.rs)
iterates a fixed array of paths and does return Err(e) on the first
non-NotFound failure — before reaching the persist::quarantined_files
loop that removes the .corrupt-* files.
clear_impl calls this as part of the privacy wipe. The .corrupt-* files hold
a full serialized history, i.e. the user's typed readings and surfaces. So a
failure removing an earlier member — a stubborn .v1.bak under EACCES, say —
leaves those on disk untouched. The error is surfaced (clear defers and returns
it), so it is not silent, but the wipe is incomplete in a way it need not be.
Fix
Make the function deferred-error: attempt every member, remember the first
error, return it at the end — matching the discipline clear_impl itself
already uses ten lines below. Ordering matters too: the members that hold user
text should not sit behind ones that do not.
Add a test that a stubborn earlier member does not prevent the .corrupt-*
sweep.
Related: write_atomic does not clean up its own tmp
Same family, likely the same fix session. persist::write_atomic leaves
<path>.tmp behind whenever write_all / sync_all / rename fails after
File::create, and nothing sweeps it at startup. A real instance is sitting in
my own profile right now — user_history.lxud.wal.tmp, 6.5 KB, dated 2026-04-11
— which is a WAL fragment, i.e. it does contain input text, and no wipe or
recovery path removes it. Either write_atomic should unlink its tmp on the
failure paths, or the startup sweep should cover <member>.tmp uniformly
instead of the two checkpoint-specific names it lists today.
Found during the /simplify and /lexime-review passes on #320 (#312) and
deliberately not bundled there — an unrelated behavior change in a persistence
PR.
remove_recovery_artifacts(engine/crates/lex-core/src/user_history/recovery.rs)iterates a fixed array of paths and does
return Err(e)on the firstnon-
NotFoundfailure — before reaching thepersist::quarantined_filesloop that removes the
.corrupt-*files.clear_implcalls this as part of the privacy wipe. The.corrupt-*files holda full serialized history, i.e. the user's typed readings and surfaces. So a
failure removing an earlier member — a stubborn
.v1.bakunder EACCES, say —leaves those on disk untouched. The error is surfaced (clear defers and returns
it), so it is not silent, but the wipe is incomplete in a way it need not be.
Fix
Make the function deferred-error: attempt every member, remember the first
error, return it at the end — matching the discipline
clear_implitselfalready uses ten lines below. Ordering matters too: the members that hold user
text should not sit behind ones that do not.
Add a test that a stubborn earlier member does not prevent the
.corrupt-*sweep.
Related:
write_atomicdoes not clean up its own tmpSame family, likely the same fix session.
persist::write_atomicleaves<path>.tmpbehind wheneverwrite_all/sync_all/renamefails afterFile::create, and nothing sweeps it at startup. A real instance is sitting inmy own profile right now —
user_history.lxud.wal.tmp, 6.5 KB, dated 2026-04-11— which is a WAL fragment, i.e. it does contain input text, and no wipe or
recovery path removes it. Either
write_atomicshould unlink its tmp on thefailure paths, or the startup sweep should cover
<member>.tmpuniformlyinstead of the two checkpoint-specific names it lists today.
Found during the
/simplifyand/lexime-reviewpasses on #320 (#312) anddeliberately not bundled there — an unrelated behavior change in a persistence
PR.