fix(persist): fall back to a direct write when session.json rename crosses a file system - #2335
fix(persist): fall back to a direct write when session.json rename crosses a file system#2335vidhanio wants to merge 4 commits into
Conversation
…osses a file system
|
Warning Review limit reached
Next review available in: 27 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
ChangesJSON persistence recovery
Estimated code review effort: 3 (Moderate) | ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Greptile SummaryThe PR adds a direct-write fallback for cross-filesystem session saves and recovery from retained temporary snapshots.
Confidence Score: 4/5The PR is not yet safe to merge because a failed recovery-file deletion can still restore session state that the user explicitly cleared. clear_path_and_tmp removes the main snapshot first, and if removing the temporary snapshot then fails, load_with_recovery treats that retained file as authoritative because the main file is absent. Files Needing Attention: src/persist/io.rs
|
| Filename | Overview |
|---|---|
| src/persist/io.rs | Adds direct-write and temporary-file recovery behavior, but clear_path_and_tmp can delete the main snapshot and then fail to delete the recovery snapshot, allowing explicitly cleared state to return. |
Reviews (4): Last reviewed commit: "fix(persist): stop stale .tmp recovery f..." | Re-trigger Greptile
Only remove the temp file after the direct-write fallback actually succeeds, so a partial or failed overwrite leaves a recovery copy instead of losing the session. Also loosen the propagate-unrelated- errors test comment to not assume a specific platform error kind.
…le is invalid If the direct-write fallback truncates or corrupts session.json (or session-history.json) part way through, the good copy left behind in the matching .tmp file was never read back on load. load() and load_history() now fall back to the .tmp file when the main file is missing or fails to parse.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/persist/io.rs (1)
436-450: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winExercise the public recovery branches in the tests.
The test calls
try_load_snapshotdirectly. It does not executeload's primary-to-.tmpselection, and it provides no coverage forload_history's equivalent branch. Add tests for missing and invalid primary files throughload()andload_history(), or extract a path-injected loader and test that selection logic directly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d0b255a7-56e4-4af9-ba98-ac7f7b9c6b8b
📒 Files selected for processing (1)
src/persist/io.rs
…d or unsupported sessions clear() and clear_history() only removed the main file, leaving a stale .tmp behind that a later load() would treat as a recoverable snapshot and restore, even though the user had explicitly cleared the session. Fix by also removing the .tmp file on clear (and when a save disables history). Also stop treating a main file from a newer, unsupported herdr version as a recoverable failure: it is valid, just not understood yet, so falling back to a stale .tmp file for it could silently replace real data. load()/load_history() now go through a shared load_with_recovery() that only tries the .tmp file when the main file is genuinely missing or invalid.
| fn clear_path_and_tmp(path: &Path) -> std::io::Result<()> { | ||
| clear_path(path)?; | ||
| clear_path(&path.with_extension("json.tmp")) | ||
| } |
There was a problem hiding this comment.
Failed clear retains recoverable state
When removing the .json.tmp recovery file fails after the main snapshot has been deleted, clear_path_and_tmp leaves the temporary snapshot intact, causing the next load to restore session or history state that the user explicitly cleared.
| fn clear_path_and_tmp(path: &Path) -> std::io::Result<()> { | |
| clear_path(path)?; | |
| clear_path(&path.with_extension("json.tmp")) | |
| } | |
| fn clear_path_and_tmp(path: &Path) -> std::io::Result<()> { | |
| clear_path(&path.with_extension("json.tmp"))?; | |
| clear_path(path) | |
| } |
Knowledge Base Used: Server Process and Headless Mode
|
this feels like a very niche weird edge case that become a 220 line of round fixes, i don't see why you would only mount the session-file but not the whole dir, so sorry but i'm going to close it. |
|
I use impermanence with nix (wipe on reboot) and would prefer to only persist what is absolutely required, excluding other files in the herdr config. I have just realized that impermanence has a mount |
Herdr saves
session.jsonby writing a temp file next to it, then renaming the temp file onto the target. Whensession.jsonis a mounted file from another drive (e.g. a bind mount), the rename fails withResourceBusyorCrossesDevicesand the save is silently dropped (only a trace-log error, no user-visible message).Fall back to a direct, non-atomic write when the rename fails for either of these reasons.
Verified with a real bind-mounted tmpfs file reproducing the issue, plus new unit tests.