Skip to content

feat: desktop FUSE data-loss bugs and replay hardening#493

Merged
FSM1 merged 20 commits into
mainfrom
feat/desktop-fuse-data-loss-bugs-replay-hardening
Jun 15, 2026
Merged

feat: desktop FUSE data-loss bugs and replay hardening#493
FSM1 merged 20 commits into
mainfrom
feat/desktop-fuse-data-loss-bugs-replay-hardening

Conversation

@FSM1

@FSM1 FSM1 commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Phase 46 — Desktop FUSE data-loss bugs + replay hardening

Closes the desktop FUSE write-durability work deferred by Phase 45: three data-loss/durability fixes, two replay-path hardening follow-ups from PR #491, the remaining Rust test coverage (Phase 45 Tier 2), plus cross-platform parity for stale-mount recovery (macOS). Behavior-changing correctness/durability work.

Changes

Stale-mount recovery (Linux + macOS)

  • feat(fuse): auto-recover stale Linux FUSE mount on startup — a crashed daemon leaves a disconnected mount where stat() returns ENOTCONN (so exists() lies) and create_dir_all then fails with EEXIST. Detect authoritatively via /proc/self/mountinfo and clear via fusermount3 -u (then lazy -z -u); plus a create_mount_point_dir EEXIST recover-then-retry belt-and-suspenders.
  • feat(fuse): auto-recover stale FUSE-T mount on macOS startup — the macOS (FUSE-T/SMB) analog: a crash can leave ~/CipherBox as a stale smbfs mount backed by a dead in-process server. Detect via mount(8) and clear via umount, then diskutil unmount force. Windows (WinFsp) self-heals — its FSD evicts the volume on host death — so no recovery is needed there.

Replay-path hardening (PR #491 follow-ups)

  • fix(fuse): park legacy empty file-meta-ipns replay entries instead of publishing an empty FilePointer.
  • fix(fuse): strict cache-bypassing IPNS resolve in replay classification so transient failures retain the entry.
  • fix(fuse): fail closed on malformed sequenceresolve_sequence_strict now returns Err on an unparseable sequence_number instead of unwrap_or(0), so a bad payload retains the entry (addresses CodeRabbit review).

Durability characterization + Tier-2 tests

Verification

  • cargo test -p cipherbox-fuse --features fuse60 passed; cargo clippy --features fuse --tests clean; cargo check for the fuse + desktop crates clean.
  • Security: 12/12 threats verified closed (46-SECURITY.md).
  • Goal-backward verify: 6/6 requirements met (46-VERIFICATION.md).
  • CodeRabbit: all review threads resolved (test temp-dir isolation; malformed-sequence fail-closed).

Crash-recovery validation (both platforms confirmed)

  • macOS — verified via a live test: stood up a real mount, reproduced a stuck/busy mount where plain umount returns Resource busy, and confirmed recover_stale_mount cleared it via the diskutil unmount force fallback. The mount(8) parser is also unit-tested against the smbfs line format.
  • Linux — confirmed directly on real Linux hardware (mount → kill -9 → restart → clean auto-recovery), validating the ENOTCONN / exists()-lies / EEXIST signature and fusermount3 recovery the design hinges on.

Notes

🤖 Generated with Claude Code

FSM1 and others added 15 commits June 15, 2026 04:41
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add pub use reply::ReplySender so cipherbox-fuse test code can implement a capturing sender
- mod reply stays private; only the trait name is surfaced
- channel.rs FUSE-T patch untouched

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- New cfg(all(test, feature = fuse)) test_support module
- make_test_fs / make_test_fs_with_keypair build the full CipherBoxFS literal
- Per-test isolated journal dir keyed by process id plus monotonic counter
- CaptureSender implements fuser::ReplySender; reply_error_code decodes out-header

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- getattr_returns_ok_for_root and flush_returns_ok prove CaptureSender captures the out-header error code
- build_upload_journal_entry round-trip asserts ciphertext is journalled and the file key is ECIES-wrapped
- build_mkdir_journal_entry round-trip asserts child and parent IPNS keys are ECIES-wrapped hex
- builder tests use a real secp256k1 keypair; no raw keys or plaintext enter the journal

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@FSM1, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 32 minutes and 40 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9bb95e13-82af-447e-a4ce-8d51d92de45c

📥 Commits

Reviewing files that changed from the base of the PR and between f73305e and f36a6ba.

📒 Files selected for processing (2)
  • apps/desktop/src-tauri/src/fuse/mod.rs
  • crates/fuse/src/lib.rs

Walkthrough

Phase 46 implements four plans for the desktop FUSE subsystem: a test harness built on a vendored ReplySender re-export and a new test_support module; Linux stale-mount auto-recovery via /proc/self/mountinfo parsing with fusermount3 fallback, plus parallel macOS recovery via mount(8) output parsing; IPNS replay hardening via a strict (no-cache-fallback) resolve method and a legacy-entry parking guard; and durability characterization tests for mkdir/release ordering and WriteQueue ciphertext round-trips, alongside comprehensive Phase 46 planning documentation including research, security audit, validation strategy, and verification report.

Changes

Phase 46: Desktop FUSE Data-Loss & Replay Hardening

Layer / File(s) Summary
FUSE test harness: ReplySender re-export and test_support module
apps/desktop/src-tauri/vendor/fuser/src/lib_impl.rs, crates/fuse/src/lib.rs, crates/fuse/src/test_support.rs, crates/fuse/src/journal_helpers.rs
Adds pub use reply::ReplySender to the vendored fuser crate, then creates a test-only test_support module providing make_isolated_journal_dir, make_test_fs, make_test_fs_with_keypair, CaptureSender, and reply_error_code, all gated behind cfg(all(test, feature = "fuse")). Adds journal_helpers builder round-trip tests using real EC keypairs to validate ciphertext/base64 output and wrapped key material.
Linux and macOS stale-mount recovery
crates/fuse/src/platform/linux.rs, crates/fuse/src/platform/macos.rs, apps/desktop/src-tauri/src/fuse/mod.rs
Adds octal-escape mountinfo parsing, recover_stale_mount, and create_mount_point_dir (EEXIST retry-once wrapper) to the Linux platform module. Adds parallel macOS recovery via mount(8) output parsing with umount and diskutil unmount force fallback. Wires both into mount_filesystem before the exists()/create_dir_all decision.
Replay hardening: strict IPNS resolve and legacy-entry parking
crates/fuse/src/lib.rs
Adds PublishCoordinator::resolve_sequence_strict (returns Err on any resolve failure, no cache fallback) and switches resolve_ipns_for_replay to call it. Adds an early None-guard in replay_upload_entry to park legacy UploadFile entries missing file_meta_ipns_name. Includes tests for empty-name merge collisions, None-name parking, strict resolve cache bypass, and transient-failure entry retention.
Durability characterization tests: mkdir/release ordering and WriteQueue persistence
crates/fuse/src/lib.rs
Adds FUSE handler harness tests validating reply behavior via test_support. Includes characterization tests for mkdir journal-before-reply and MkdirConflict re-arm, release journal ordering and ciphertext persistence before cleanup, and a WriteQueue round-trip test validating ciphertext reload matches original bytes.
Phase 46 planning documentation (Plans 01–04, research, security, validation, verification)
.planning/phases/46-.../46-0[1-4]-PLAN.md, .planning/phases/46-.../46-0[1-4]-SUMMARY.md, .planning/phases/46-.../46-RESEARCH.md, .planning/phases/46-.../46-SECURITY.md, .planning/phases/46-.../46-VALIDATION.md, .planning/phases/46-.../46-VERIFICATION.md
Complete Phase 46 planning suite: four plan documents with TDD task definitions and threat models; research document covering all six requirements, architectural responsibility, and validation strategy; security audit document with ASVS level, threat verification table (T-46-01 through T-46-10, T-46-SC), and accepted risks (AR-46-05, AR-46-SC); validation strategy with test command matrices and manual UAT procedures; verification report with evidence tables, test spot-checks, Linux-only human verification gate, and accepted limitations.
Phase completion status and developer notes
.planning/ROADMAP.md, .planning/STATE.md, apps/desktop/CLAUDE.md
Updates ROADMAP.md to replace Phase 46 placeholder with explicit 4-plan checklist. Updates STATE.md to mark overall project as "Phase 46 secured and verified" at 134/134 plans (100%). Updates CLAUDE.md macOS known-limitations section to document automatic stale-mount recovery behavior on startup.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(200, 220, 255, 0.5)
    Note over mount_filesystem,fusermount3: Linux stale-mount recovery path
  end
  participant mount_filesystem
  participant recover_stale_mount
  participant mountinfo_contains_mountpoint
  participant fusermount3
  participant create_mount_point_dir

  mount_filesystem->>recover_stale_mount: recover_stale_mount(&mount_path)
  recover_stale_mount->>mountinfo_contains_mountpoint: parse /proc/self/mountinfo
  mountinfo_contains_mountpoint-->>recover_stale_mount: mount found (stale)
  recover_stale_mount->>fusermount3: fusermount3 -u <path>
  fusermount3-->>recover_stale_mount: error
  recover_stale_mount->>fusermount3: fusermount3 -z -u <path>
  fusermount3-->>recover_stale_mount: ok
  mount_filesystem->>create_mount_point_dir: create_mount_point_dir(&mount_path)
  create_mount_point_dir->>create_mount_point_dir: create_dir_all → EEXIST → recover + retry once
  create_mount_point_dir-->>mount_filesystem: Ok(())
Loading
sequenceDiagram
  rect rgba(255, 220, 200, 0.5)
    Note over replay_upload_entry,record_failure: Replay hardening paths
  end
  participant replay_upload_entry
  participant resolve_ipns_for_replay
  participant resolve_sequence_strict
  participant ApiClient
  participant record_failure

  replay_upload_entry->>replay_upload_entry: file_meta_ipns_name is None?
  alt entry has no per-file IPNS name
    replay_upload_entry->>record_failure: Err → park entry (legacy guard)
  else entry has per-file IPNS name
    replay_upload_entry->>resolve_ipns_for_replay: classify outcome
    resolve_ipns_for_replay->>resolve_sequence_strict: strict resolve (no cache fallback)
    resolve_sequence_strict->>ApiClient: IPNS resolve request
    ApiClient-->>resolve_sequence_strict: transient error
    resolve_sequence_strict-->>resolve_ipns_for_replay: Err (no cache fallback)
    resolve_ipns_for_replay-->>replay_upload_entry: IpnsResolveOutcome::Error
    replay_upload_entry->>record_failure: park entry for later retry
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • FSM1/cipher-box#491: Introduced file_meta_ipns_name: Option<String> and IpnsResolveOutcome/resolve_ipns_for_replay plumbing in crates/fuse/src/lib.rs; Phase 46 extends replay semantics by handling None per-file IPNS names and tightening resolve behavior using that same infrastructure.
  • FSM1/cipher-box#487: Modified FUSE mount/replay pipeline around crates/fuse/src/lib.rs for durable replay-on-mount semantics; Phase 46 builds on that foundation by hardening replay behavior with strict resolve and legacy-entry parking.
  • FSM1/cipher-box#449: Fixed cached IPNS sequenceNumber mismatches and seeded PublishCoordinator during mount; Phase 46 strengthens replay IPNS resolving (e.g., resolve_sequence_strict and legacy replay parking) so replay semantics depend on correct sequence/resolve outcomes.

Suggested labels

release:desktop:fix, release:cipherbox-fuse:fix

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: desktop FUSE data-loss bugs and replay hardening' clearly and concisely summarizes the main focus of the PR: addressing FUSE-related data-loss bugs and improving replay robustness through hardening.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/desktop-fuse-data-loss-bugs-replay-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added release:desktop:feat Minor version bump (new feature) for desktop release:cipherbox-fuse:feat Minor version bump (new feature) for cipherbox-fuse labels Jun 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Release Preview

Package Bump Label Source
cipherbox-fuse minor release:cipherbox-fuse:feat Direct (feat commit)
desktop minor release:desktop:feat Direct (feat commit)

@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.42386% with 61 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.83%. Comparing base (962b1e9) to head (f36a6ba).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
crates/fuse/src/platform/linux.rs 61.32% 41 Missing ⚠️
apps/desktop/src-tauri/src/fuse/mod.rs 0.00% 10 Missing ⚠️
crates/fuse/src/lib.rs 97.16% 10 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #493      +/-   ##
==========================================
+ Coverage   62.85%   66.83%   +3.98%     
==========================================
  Files         139      155      +16     
  Lines       10359    16843    +6484     
  Branches     1125     1127       +2     
==========================================
+ Hits         6511    11257    +4746     
- Misses       3612     5350    +1738     
  Partials      236      236              
Flag Coverage Δ
api 84.78% <ø> (+0.04%) ⬆️
api-client 84.78% <ø> (+0.04%) ⬆️
core 84.78% <ø> (+0.04%) ⬆️
crypto 84.78% <ø> (+0.04%) ⬆️
desktop 18.21% <0.00%> (-13.06%) ⬇️
rust 63.72% <91.86%> (?)
sdk 84.78% <ø> (+0.04%) ⬆️
sdk-core 84.78% <ø> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
crates/fuse/src/journal_helpers.rs 90.28% <100.00%> (ø)
crates/fuse/src/test_support.rs 100.00% <100.00%> (ø)
apps/desktop/src-tauri/src/fuse/mod.rs 16.23% <0.00%> (+16.23%) ⬆️
crates/fuse/src/lib.rs 49.49% <97.16%> (ø)
crates/fuse/src/platform/linux.rs 43.33% <61.32%> (ø)

... and 69 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src-tauri/src/fuse/mod.rs (1)

101-124: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Recovered !exists path skips stale mount-directory cleanup

After Linux recovery/retry succeeds from the !mount_path.exists() branch, stale mountpoint contents are not cleaned because cleanup only exists in the else branch. This leaves crash residue on the recover path.

Proposed fix
-    if !mount_path.exists() {
+    if !mount_path.exists() {
         // On Linux this recovers once from a stale FUSE mount whose dirent still
         // exists (surfaces as EEXIST even though `exists()` returned false); on
         // other platforms it is a plain `create_dir_all`.
         #[cfg(target_os = "linux")]
         let create_result = cipherbox_fuse::platform::linux::create_mount_point_dir(&mount_path);
         #[cfg(not(target_os = "linux"))]
         let create_result = std::fs::create_dir_all(&mount_path);
         create_result.map_err(|e| format!("Failed to create mount point: {}", e))?;
         #[cfg(unix)]
         {
             use std::os::unix::fs::PermissionsExt;
             let _ = std::fs::set_permissions(&mount_path, std::fs::Permissions::from_mode(0o700));
         }
-    } else {
-        if let Ok(entries) = std::fs::read_dir(&mount_path) {
-            for entry in entries.flatten() {
-                let path = entry.path();
-                if path.is_dir() { let _ = std::fs::remove_dir_all(&path); }
-                else { let _ = std::fs::remove_file(&path); }
-            }
-            log::info!("Cleaned stale mount point: {}", mount_path.display());
-        }
     }
+
+    if let Ok(entries) = std::fs::read_dir(&mount_path) {
+        for entry in entries.flatten() {
+            let path = entry.path();
+            if path.is_dir() { let _ = std::fs::remove_dir_all(&path); }
+            else { let _ = std::fs::remove_file(&path); }
+        }
+        log::info!("Cleaned stale mount point: {}", mount_path.display());
+    }

As per coding guidelines, “App should clean stale mount contents before mounting FUSE vault at ~/CipherBox to handle .DS_Store files from previous crashes”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src-tauri/src/fuse/mod.rs` around lines 101 - 124, The stale
mount point cleanup code that removes old entries is only executed in the else
branch when mount_path exists, but it should also run after successfully
creating the mount point directory in the !mount_path.exists() branch to handle
crash residue on the Linux recovery path. After the create_result.map_err call
succeeds in the !mount_path.exists() block, add the same cleanup logic that
currently exists in the else branch (the read_dir loop that removes stale files
and directories, followed by the log message "Cleaned stale mount point") to
ensure stale mount contents are cleaned regardless of which path is taken.

Source: Coding guidelines

🧹 Nitpick comments (1)
crates/fuse/src/lib.rs (1)

2982-2985: ⚡ Quick win

Avoid fixed sleep in this async durability test to reduce flakiness.

Line 2984 uses a fixed 200ms sleep before draining completions; slower CI runners can miss the window. Prefer bounded polling until the expected condition or timeout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/fuse/src/lib.rs` around lines 2982 - 2985, Replace the fixed
std::thread::sleep call with bounded polling to eliminate test flakiness.
Instead of sleeping for a fixed 200 milliseconds before calling
fs.drain_upload_completions(), implement a loop that repeatedly checks for the
expected condition (that the entry has been retained and is ready to be drained)
with a reasonable interval between checks, continuing until either the condition
is met or a timeout is reached. This approach is more robust across CI
environments with varying performance characteristics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/fuse/src/lib.rs`:
- Around line 305-321: The resolve_sequence_strict method uses unwrap_or(0) when
parsing the sequence_number, which silently treats malformed payloads as 0 and
returns success instead of failing. In strict replay mode, this must fail
closed. Replace the unwrap_or(0) call on the parse::<u64>() operation with
proper error handling that converts any parse failure into a Result error and
propagates it up, ensuring that invalid sequence numbers are rejected rather
than silently converted to 0.

---

Outside diff comments:
In `@apps/desktop/src-tauri/src/fuse/mod.rs`:
- Around line 101-124: The stale mount point cleanup code that removes old
entries is only executed in the else branch when mount_path exists, but it
should also run after successfully creating the mount point directory in the
!mount_path.exists() branch to handle crash residue on the Linux recovery path.
After the create_result.map_err call succeeds in the !mount_path.exists() block,
add the same cleanup logic that currently exists in the else branch (the
read_dir loop that removes stale files and directories, followed by the log
message "Cleaned stale mount point") to ensure stale mount contents are cleaned
regardless of which path is taken.

---

Nitpick comments:
In `@crates/fuse/src/lib.rs`:
- Around line 2982-2985: Replace the fixed std::thread::sleep call with bounded
polling to eliminate test flakiness. Instead of sleeping for a fixed 200
milliseconds before calling fs.drain_upload_completions(), implement a loop that
repeatedly checks for the expected condition (that the entry has been retained
and is ready to be drained) with a reasonable interval between checks,
continuing until either the condition is met or a timeout is reached. This
approach is more robust across CI environments with varying performance
characteristics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0e76f919-9a41-4b2c-9d0e-efa19c3086b3

📥 Commits

Reviewing files that changed from the base of the PR and between 4b539b4 and 363e392.

📒 Files selected for processing (20)
  • .planning/ROADMAP.md
  • .planning/STATE.md
  • .planning/phases/46-desktop-fuse-data-loss-bugs-replay-hardening/46-01-PLAN.md
  • .planning/phases/46-desktop-fuse-data-loss-bugs-replay-hardening/46-01-SUMMARY.md
  • .planning/phases/46-desktop-fuse-data-loss-bugs-replay-hardening/46-02-PLAN.md
  • .planning/phases/46-desktop-fuse-data-loss-bugs-replay-hardening/46-02-SUMMARY.md
  • .planning/phases/46-desktop-fuse-data-loss-bugs-replay-hardening/46-03-PLAN.md
  • .planning/phases/46-desktop-fuse-data-loss-bugs-replay-hardening/46-03-SUMMARY.md
  • .planning/phases/46-desktop-fuse-data-loss-bugs-replay-hardening/46-04-PLAN.md
  • .planning/phases/46-desktop-fuse-data-loss-bugs-replay-hardening/46-04-SUMMARY.md
  • .planning/phases/46-desktop-fuse-data-loss-bugs-replay-hardening/46-RESEARCH.md
  • .planning/phases/46-desktop-fuse-data-loss-bugs-replay-hardening/46-SECURITY.md
  • .planning/phases/46-desktop-fuse-data-loss-bugs-replay-hardening/46-VALIDATION.md
  • .planning/phases/46-desktop-fuse-data-loss-bugs-replay-hardening/46-VERIFICATION.md
  • apps/desktop/src-tauri/src/fuse/mod.rs
  • apps/desktop/src-tauri/vendor/fuser/src/lib_impl.rs
  • crates/fuse/src/journal_helpers.rs
  • crates/fuse/src/lib.rs
  • crates/fuse/src/platform/linux.rs
  • crates/fuse/src/test_support.rs

Comment thread crates/fuse/src/lib.rs
FSM1 and others added 4 commits June 15, 2026 15:21
Mirror the Linux recover_stale_mount on macOS. A crashed FUSE-T session
can leave ~/CipherBox as a stale smbfs mount backed by a dead in-process
server, blocking remount until force-unmounted. Detect via mount(8) and
clear via umount, then diskutil unmount force as a fallback. Best-effort
and run before the exists()/create_dir_all decision, matching Linux.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@FSM1

FSM1 commented Jun 15, 2026

Copy link
Copy Markdown
Owner Author

Addressed the outside-diff-range finding from the CodeRabbit review in 2eda3d445: the stale mount-contents cleanup (read_dir loop) was moved out of the else branch so it runs on every path, including the Linux recovery sub-path (stale mount → exists() lies → create_mount_point_dir EEXIST retry) that previously skipped it. No-op on a freshly created empty dir. cargo check -p cipherbox-desktop --features fuse clean.

@FSM1

FSM1 commented Jun 15, 2026

Copy link
Copy Markdown
Owner Author

Also addressed the nitpick from the same review (fixed 200ms sleep in the release-durability test) in f36a6ba0b: replaced the sleep with a bounded poll-drain (≤2s, 20ms steps) that waits until the upload failure is actually recorded (retries >= 1), then asserts retention. This removes the CI-timing flakiness and strengthens the test — it now verifies the failure path ran rather than just that the entry exists. cargo test -p cipherbox-fuse --features fuse: 60 passed.

@FSM1 FSM1 merged commit 79de97b into main Jun 15, 2026
27 checks passed
@FSM1 FSM1 deleted the feat/desktop-fuse-data-loss-bugs-replay-hardening branch June 15, 2026 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:cipherbox-fuse:feat Minor version bump (new feature) for cipherbox-fuse release:desktop:feat Minor version bump (new feature) for desktop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant