Skip to content

fix(desktop): trigger metadata refresh from lookup/open, fix e2e sync test#456

Merged
FSM1 merged 4 commits into
mainfrom
fix/desktop-e2e-cross-client-sync-poll
Apr 14, 2026
Merged

fix(desktop): trigger metadata refresh from lookup/open, fix e2e sync test#456
FSM1 merged 4 commits into
mainfrom
fix/desktop-e2e-cross-client-sync-poll

Conversation

@FSM1

@FSM1 FSM1 commented Apr 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • Root cause: The cross-client sync e2e test (added in fix(desktop): detect remote file edits and re-resolve IPNS in FUSE mount #454) polled with stat + cat but never did ls on the mount directory. Only readdir (triggered by ls) calls drain_refresh_completions()populate_folder(), which is the only code path that detects modified_at changes on FilePointers and triggers IPNS re-resolution. Without it, the FUSE mount never detected remote edits during the 120s polling window.

  • FUSE layer fix: handle_lookup (macOS/Linux) and handle_open (Windows) now also check for stale parent folder metadata and spawn background refresh — matching readdir's existing behaviour. This means file access (stat, open, cat) will eventually pick up remote edits without requiring an explicit directory listing.

  • Test fix: Added ls to the Test 5 polling loop in the bash script (most reliable trigger since DIR_TTL=0 means the kernel never caches directory results).

  • Windows parity: Added the missing test-cross-client-sync.ps1 and wired it into run-all.ps1 as Step 6.

Changed files

File Change
crates/fuse/src/read_ops.rs handle_lookup now checks parent folder metadata staleness and spawns background refresh for loaded folders (not just lazy-load)
crates/fuse/src/platform/windows/read_ops.rs handle_open now calls drain_refresh_completions() and checks parent folder staleness
tests/desktop-e2e/scripts/test-cross-client-sync.sh Added ls to Test 5 polling loop to trigger readdir
tests/desktop-e2e/scripts/test-cross-client-sync.ps1 New Windows PowerShell version of cross-client sync test
tests/desktop-e2e/scripts/run-all.ps1 Added Step 6 (cross-client sync) to Windows test suite

Test plan

  • Desktop E2E tests pass on macOS (cross-client sync Test 5 no longer times out)
  • Desktop E2E tests pass on Linux (same fix)
  • Desktop E2E tests pass on Windows (new PS1 test runs as Step 6)
  • Existing FUSE unit tests pass (37/37 verified locally)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Automatic background refresh for folder metadata now triggers from more operations, improving mount consistency and timely updates.
  • Bug Fixes

    • Prevents duplicate concurrent metadata refreshes and better clears in-flight refresh state for more reliable sync visibility.
  • Tests

    • Added end-to-end cross-client sync tests and orchestration for Windows and macOS to validate updates across clients.

… test

The cross-client sync e2e test polled with stat+cat but never triggered
readdir, which is the only FUSE operation that called
drain_refresh_completions() -> populate_folder(). This meant remote edits
were never detected during the 120s polling window.

Fix the test by adding ls (readdir) to the polling loop, and more
importantly fix the FUSE layer so that lookup (macOS/Linux) and open
(Windows) also check for stale parent folder metadata and spawn
background refresh — matching readdir's existing behaviour. This ensures
file access eventually picks up remote edits without requiring an
explicit directory listing.

Also adds the missing Windows PowerShell cross-client sync test and
wires it into run-all.ps1 as Step 6.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 89794793ed09
Copilot AI review requested due to automatic review settings April 13, 2026 22:11
@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@FSM1 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 46 minutes and 17 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 46 minutes and 17 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: df7f7b97-4dba-4c8b-a254-4e5f4bd7f38e

📥 Commits

Reviewing files that changed from the base of the PR and between d83249e and 32b8e93.

📒 Files selected for processing (5)
  • crates/fuse/src/dir_ops.rs
  • crates/fuse/src/lib.rs
  • crates/fuse/src/platform/windows/dir_ops.rs
  • crates/fuse/src/platform/windows/read_ops.rs
  • crates/fuse/src/read_ops.rs

Walkthrough

Background refresh logic was added to FUSE handlers: stale parent-folder metadata now triggers non-blocking tasks that resolve IPNS, fetch encrypted folder metadata from IPFS, decrypt it, and emit PendingRefresh updates; new cross-client sync end-to-end tests were also added.

Changes

Cohort / File(s) Summary
FUSE core state
crates/fuse/src/lib.rs, apps/desktop/src-tauri/src/fuse/mod.rs, apps/desktop/src-tauri/src/fuse/windows/mod.rs
Added refreshing_metadata: HashSet<String> to CipherBoxFS and initialized it; drain logic now removes IPNS names from this set when refresh completions are processed.
Windows FUSE handlers
crates/fuse/src/platform/windows/read_ops.rs, crates/fuse/src/platform/windows/dir_ops.rs
handle_open/dir readdir now check refreshing_metadata, insert IPNS names before spawning background tasks, drain refresh completions earlier, and snapshot/release inode borrows to avoid borrow conflicts.
Cross-platform FUSE handlers
crates/fuse/src/read_ops.rs, crates/fuse/src/dir_ops.rs
handle_lookup and readdir updated to detect stale/root metadata, compute (needs_load, needs_refresh), and spawn guarded non-blocking background refresh tasks that resolve IPNS, fetch/decrypt metadata, and enqueue PendingRefresh.
E2E test orchestration
tests/desktop-e2e/scripts/run-all.ps1
Added "Step 6: Cross-client sync" orchestration that runs the new cross-client sync test, captures exit code, and folds results into overall suite summary.
Cross-client sync tests
tests/desktop-e2e/scripts/test-cross-client-sync.ps1, tests/desktop-e2e/scripts/test-cross-client-sync.sh
Added a Windows PowerShell end-to-end test with five phases (create, verify via SDK, edit via SDK, re-verify, poll mount for edited content). Shell script adjusted Test 5 to trigger readdir via ls before stat/cat to drive refresh.

Sequence Diagram(s)

sequenceDiagram
    participant Handler as FUSE Handler
    participant Cache as Metadata Cache
    participant IPNS as IPNS Resolver
    participant IPFS as IPFS Gateway
    participant Crypto as Decryption
    participant Channel as refresh_tx Channel

    Handler->>Cache: Check parent metadata (stale/missing?)
    alt stale or missing
        Handler->>Handler: Insert ipns_name into refreshing_metadata
        Handler->>Handler: Spawn background task (non-blocking)
        Handler-->>Handler: Return to caller

        rect rgba(100,150,200,0.5)
        Note over Handler,Channel: Background task
        Handler->>IPNS: Resolve parent IPNS name
        alt resolution succeeds
            IPNS->>IPFS: Fetch encrypted metadata (CID)
            alt fetch succeeds
                IPFS->>Crypto: Decrypt with folder_key
                alt decrypt succeeds
                    Crypto->>Channel: Send PendingRefresh { ino, ipns_name, metadata, cid }
                else decrypt fails
                    Crypto->>Handler: Log warning/debug
                end
            else fetch fails
                IPFS->>Handler: Log warning/debug
            end
        else resolution fails
            IPNS->>Handler: Log warning/debug
        end
        end
    else metadata fresh
        Handler->>Cache: Use cached metadata
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main changes: fixing metadata refresh triggering in lookup/open operations and fixing the e2e sync test, which matches the core objectives of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 fix/desktop-e2e-cross-client-sync-poll

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:cipherbox-fuse:fix Patch version bump (bug fix) for cipherbox-fuse release:desktop:fix Patch version bump (bug fix) for desktop labels Apr 13, 2026
@github-actions

github-actions Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Release Preview

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes a desktop cross-client sync gap where remote edits weren’t detected during polling unless a directory listing occurred, and updates desktop E2E coverage to reliably trigger refresh logic (including Windows parity).

Changes:

  • Trigger stale folder-metadata refresh from file-access paths (lookup on Unix/macOS and open on Windows), not only from directory listing operations.
  • Fix the cross-client sync E2E polling loop to explicitly trigger readdir (ls / Get-ChildItem) during polling.
  • Add and wire a Windows PowerShell cross-client sync test into the Windows desktop E2E runner.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/fuse/src/read_ops.rs Adds stale-parent metadata refresh triggering from handle_lookup to align file access with existing readdir refresh behavior.
crates/fuse/src/platform/windows/read_ops.rs Drains refresh completions on open and triggers stale-parent metadata refresh from handle_open for Windows parity.
tests/desktop-e2e/scripts/test-cross-client-sync.sh Adds ls in the polling loop to reliably trigger refresh processing during E2E sync checks.
tests/desktop-e2e/scripts/test-cross-client-sync.ps1 Introduces Windows version of the cross-client sync E2E test.
tests/desktop-e2e/scripts/run-all.ps1 Adds Step 6 to run the new cross-client sync test on Windows.
Cargo.lock Updates the locked workspace package version for cipherbox-fuse.

Comment thread crates/fuse/src/read_ops.rs
Comment thread crates/fuse/src/platform/windows/read_ops.rs
Comment thread tests/desktop-e2e/scripts/test-cross-client-sync.sh Outdated
Comment thread tests/desktop-e2e/scripts/test-cross-client-sync.ps1 Outdated
@codecov

codecov Bot commented Apr 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 71.60%. Comparing base (09e6830) to head (32b8e93).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
apps/desktop/src-tauri/src/fuse/mod.rs 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #456      +/-   ##
==========================================
+ Coverage   62.34%   71.60%   +9.26%     
==========================================
  Files         135      114      -21     
  Lines       10157     7262    -2895     
  Branches     1081     1083       +2     
==========================================
- Hits         6332     5200    -1132     
+ Misses       3601     1838    -1763     
  Partials      224      224              
Flag Coverage Δ
api 84.72% <ø> (+0.05%) ⬆️
api-client 84.72% <ø> (+0.05%) ⬆️
core 84.72% <ø> (+0.05%) ⬆️
crypto 84.72% <ø> (+0.05%) ⬆️
desktop 14.21% <0.00%> (-17.05%) ⬇️
sdk 84.72% <ø> (+0.05%) ⬆️
sdk-core 84.72% <ø> (+0.05%) ⬆️

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

Files with missing lines Coverage Δ
apps/desktop/src-tauri/src/fuse/mod.rs 12.88% <0.00%> (+12.88%) ⬆️

... and 34 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.

🧹 Nitpick comments (1)
crates/fuse/src/platform/windows/read_ops.rs (1)

117-175: Coalesce stale-folder refreshes before spawning network work.

This branch will keep spawning identical resolve/fetch/decrypt tasks while metadata_cache.get(..) stays empty. On Windows, bursty open() traffic can hit the same stale path many times before the first refresh lands. Please add an in-flight guard for folder refreshes, similar to prefetching / resolving_file_pointers, so one stale folder only has one background refresh at a time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fuse/src/platform/windows/read_ops.rs` around lines 117 - 175, This
spawns duplicate resolve/fetch/decrypt work while metadata_cache.get(...) is
empty; add an in-flight guard so only one background refresh per folder runs:
before creating stale_info-driven task, consult and atomically insert into a
per-Fs in-flight set (e.g. add a HashSet/ConcurrentHashSet field like
refreshing_folders or reuse the existing prefetching/resolving_file_pointers
structure) keyed by ipns_name (or refresh_ino+ipns_name); only call rt.spawn and
start the cipherbox_api_client::ipns::resolve_ipns / ipfs::fetch_content /
decrypt sequence if the insert succeeded, and ensure the spawned task removes
the key from the in-flight set on all completion paths (after sending
crate::PendingRefresh via refresh_tx or on any error) so subsequent opens can
trigger a new refresh once the first finishes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/fuse/src/platform/windows/read_ops.rs`:
- Around line 117-175: This spawns duplicate resolve/fetch/decrypt work while
metadata_cache.get(...) is empty; add an in-flight guard so only one background
refresh per folder runs: before creating stale_info-driven task, consult and
atomically insert into a per-Fs in-flight set (e.g. add a
HashSet/ConcurrentHashSet field like refreshing_folders or reuse the existing
prefetching/resolving_file_pointers structure) keyed by ipns_name (or
refresh_ino+ipns_name); only call rt.spawn and start the
cipherbox_api_client::ipns::resolve_ipns / ipfs::fetch_content / decrypt
sequence if the insert succeeded, and ensure the spawned task removes the key
from the in-flight set on all completion paths (after sending
crate::PendingRefresh via refresh_tx or on any error) so subsequent opens can
trigger a new refresh once the first finishes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e85681f0-b754-4e1e-ad74-b23407e28c7c

📥 Commits

Reviewing files that changed from the base of the PR and between 09e6830 and 14315dd.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • crates/fuse/src/platform/windows/read_ops.rs
  • crates/fuse/src/read_ops.rs
  • tests/desktop-e2e/scripts/run-all.ps1
  • tests/desktop-e2e/scripts/test-cross-client-sync.ps1
  • tests/desktop-e2e/scripts/test-cross-client-sync.sh

FSM1 and others added 2 commits April 14, 2026 00:31
Add refreshing_metadata HashSet guard to prevent multiple background
IPNS resolve+fetch+decrypt tasks for the same folder when the metadata
cache is expired. Also fix inaccurate test comments claiming readdir
is the only drain_refresh_completions trigger.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 7df7df28c402
Move inode field extraction (parent_ino, children, attr) before the
mutable borrow of fs.refreshing_metadata, matching the scoped-block
pattern already used in the shared dir_ops.rs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 1860aed4d6be

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

crates/fuse/src/platform/windows/dir_ops.rs:75

  • After adding refreshing_metadata deduplication, the background refresh task only clears the guard if it successfully sends PendingRefresh. Any resolve/fetch/decrypt failure leaves the folder’s IPNS name stuck in refreshing_metadata, preventing further refreshes and potentially freezing metadata updates after a transient error. Ensure the guard is cleared on failure/timeout as well (e.g., send a failure completion or add expiry).
        if let Some((ipns_name, folder_key)) = stale_info.filter(|(n, _)| !fs.refreshing_metadata.contains(n)) {
            fs.refreshing_metadata.insert(ipns_name.clone());
            let api = fs.api.clone();
            let rt = fs.rt.clone();
            let tx = fs.refresh_tx.clone();
            let refresh_ino = ino;
            rt.spawn(async move {

crates/fuse/src/dir_ops.rs:69

  • With the new refreshing_metadata dedup guard, a refresh that fails (resolve/fetch/decrypt) will never clear the guard because only successful refreshes send PendingRefresh. That can permanently disable further metadata refreshes for this folder, leaving the mount stuck with stale entries after any transient network error. Clear the guard on failure/timeout as well (e.g., send a failure message or add an expiry/backoff mechanism).
        if let Some((ipns_name, folder_key)) = stale_info.filter(|(n, _)| offset == 0 && !fs.refreshing_metadata.contains(n)) {
            fs.refreshing_metadata.insert(ipns_name.clone());
            let api = fs.api.clone();
            let rt = fs.rt.clone();
            let tx = fs.refresh_tx.clone();
            let refresh_ino = ino;

Comment thread crates/fuse/src/lib.rs
Comment thread crates/fuse/src/read_ops.rs Outdated
@FSM1

FSM1 commented Apr 13, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai please review the changes since the last review

@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown

@FSM1, I'll kick off a fresh review of the changes since the last review on PR #456 right away!


🧠 Learnings used
Learnt from: FSM1
Repo: FSM1/cipher-box PR: 253
File: tests/e2e-desktop/scripts/test-conflict-detection.ps1:156-157
Timestamp: 2026-03-03T14:55:05.884Z
Learning: In `tests/e2e-desktop/scripts/*.{sh,ps1}` (FUSE E2E tests): Fixed sleep-based waits after file mutations are an inherent limitation of FUSE-mount testing. The FUSE daemon provides no IPC signal or status endpoint to indicate when a debounced IPNS metadata publish completes. Tests use conservative fixed waits (e.g., 8s for initial publish accounting for 1.5s debounce + 10s safety valve, 15s for conflict resolution) and can be increased if CI proves flaky. Eliminating sleep-based waits would require exposing an IPC readiness signal from the FUSE daemon.

Learnt from: CR
Repo: FSM1/cipher-box PR: 0
File: apps/desktop/CLAUDE.md:0-0
Timestamp: 2026-02-21T01:23:36.268Z
Learning: Cross-platform FUSE architecture must include platform-agnostic data structures (`InodeTable`, `MetadataCache`, `ContentCache`), channel-based async prefetch pattern, debounced publish queue with per-folder coalescing, `PublishCoordinator` for IPNS sequence management, and crypto functions (`encrypt_metadata_to_json()`, `decrypt_metadata_from_ipfs_public()`) with no OS dependencies

Learnt from: FSM1
Repo: FSM1/cipher-box PR: 296
File: packages/sdk/src/client.ts:286-360
Timestamp: 2026-03-20T23:50:55.576Z
Learning: In `packages/sdk/src/client.ts` (`CipherBoxClient`): Concurrent mutations to the same folder (e.g., `createFolder`, `renameItem`, `moveItem`, `deleteItem`, `deleteToBin`) are intentionally not serialized with a per-folder mutex in the SDK. Concurrent safety is provided at the UI layer — buttons/actions are disabled while an operation is in flight. A keyed async mutex/queue per folder IPNS name (and a separate one for bin state) is a known deferred improvement and should not be re-flagged as a regression in this or future PRs (first flagged in PR `#296`).

Learnt from: FSM1
Repo: FSM1/cipher-box PR: 426
File: .github/scripts/pr-release-preview.js:259-264
Timestamp: 2026-04-01T01:05:09.036Z
Learning: In `.github/scripts/pr-release-preview.js` (FSM1/cipher-box, PR `#426`): The `octokit.rest.repos.getCommit` call that reads `commitDetail.data.files` intentionally does NOT follow Link-header pagination for commits with >300 changed files. Individual commits in this monorepo are guaranteed to stay well under the 300-file limit, so the single-page response is sufficient. The general N+1 API optimization for this script is tracked in the phase deferred items list. Do NOT flag the absence of Link-header pagination on this `getCommit` call in future reviews.

Learnt from: FSM1
Repo: FSM1/cipher-box PR: 0
File: :0-0
Timestamp: 2026-03-30T01:40:47.052Z
Learning: In FSM1/cipher-box (`release-gate.yml`), `.github/workflows/ci-e2e.yml` is intentionally excluded from both `WEB_PATTERNS` and `DESKTOP_PATTERNS` in the release gate's change-detection step. Reason: `ci-e2e.yml` self-includes in its own `dorny/paths-filter` for both `web` and `desktop`, so any change to the orchestrator still triggers both E2E suites on `main` pushes. Including it in the release gate patterns would cause a deadlock: the gate detects "desktop changed" but `ci-e2e.yml`'s narrower per-push filter may not schedule a Desktop E2E job, leaving no run for the gate to find. The release gate only needs to block on actual app-code changes, not on CI config changes. Do NOT flag the absence of `ci-e2e.yml` from the release gate pattern lists.

Learnt from: FSM1
Repo: FSM1/cipher-box PR: 426
File: .github/scripts/pr-release-preview.js:475-532
Timestamp: 2026-03-31T22:53:16.506Z
Learning: In `FSM1/cipher-box` `.github/scripts/pr-release-preview.js` (PR `#426`, updated in commit 228749b): The label reconciliation logic compares bump levels numerically. If the computed bump is **strictly higher** than an existing `release:<component>:<type>` label, the existing label is replaced automatically (e.g., a force-push that changes a `fix` commit to `feat` will upgrade the label). If the existing label represents a **higher or equal** bump compared to the computed one, it is preserved as a manual override (e.g., a user manually sets `breaking` when the script computes `feat`). There is no auto/manual marker — the bump-level comparison is the sole heuristic. Components whose computed bump disappears entirely still have their labels removed. Do NOT flag the absence of an auto-marker or assert that existing labels are always treated as authoritative regardless of computed bump level.

Learnt from: FSM1
Repo: FSM1/cipher-box PR: 426
File: .github/scripts/pr-release-preview.js:524-532
Timestamp: 2026-04-01T01:04:18.167Z
Learning: In `.github/scripts/pr-release-preview.js` (FSM1/cipher-box PR `#426`): Labels for components whose computed bump disappears are now removed (labelsToRemove.push). This means manually-set labels for such components are also swept away on force-push. Tracking auto-applied vs manual label provenance (distinguishing the two so manual labels survive a re-run) is intentionally deferred to a future phase item. Do NOT flag the absence of auto/manual provenance tracking as an unresolved issue.

Learnt from: FSM1
Repo: FSM1/cipher-box PR: 352
File: crates/fuse/src/lib.rs:235-243
Timestamp: 2026-03-24T19:11:15.856Z
Learning: In `crates/fuse/src/lib.rs` `spawn_metadata_publish()` and `build_folder_metadata()`: The `folder_key` and `ipns_private_key` parameters are plain `Vec<u8>` rather than `Zeroizing<Vec<u8>>`. This is intentional for v1 (Phase 23 PR `#352`) — the copies are short-lived (<2s) on a background thread, and the primary zeroization defense is on the source `InodeKind` fields. Wrapping publish-path copies in `Zeroizing` would touch 15+ call sites across all platforms and is deferred to a follow-up PR. Do not re-flag this as a regression.

Learnt from: FSM1
Repo: FSM1/cipher-box PR: 253
File: apps/desktop/src-tauri/src/fuse/mod.rs:359-359
Timestamp: 2026-03-03T15:26:30.522Z
Learning: In `apps/desktop/src-tauri/src/fuse/mod.rs` `spawn_metadata_publish()`: Orphaned metadata CIDs (< 1KB JSON payloads) from failed IPNS publish attempts are acceptable for v1. On non-409 publish errors, the function returns early without unpinning the uploaded metadata CID. The debounced publish queue retries with fresh metadata, and Pinata's garbage collection eventually unpins orphaned CIDs. Explicit cleanup on every error path was deferred to v2 as the complexity vs. benefit tradeoff is not favorable for small payloads.
<!-- [add_learning]
For the cipher-box desktop FUSE implementation: The project philosophy accepts small orphaned IPFS CIDs (metadata payloads < 1KB) in error paths when adding explicit cleanup would significantly increase code complexity. Pinata's garbage collection provides the backstop for eventual cleanup.
</learning>

Learnt from: FSM1
Repo: FSM1/cipher-box PR: 419
File: README.md:23-23
Timestamp: 2026-03-31T00:16:03.167Z
Learning: In `README.md` (FSM1/cipher-box): The "30-day soft-delete" bin retention stated in the Recycle Bin feature bullet is intended policy, not yet enforced in code (no `expiresAt`/TTL field in `packages/core/src/bin/types.ts` and no cleanup job). The enforcement point and user-configurable retention period are deferred to Phase 39 (User-configurable vault parameters). Do NOT flag the absence of a TTL/cleanup mechanism for bin entries as a missing enforcement concern ahead of Phase 39.
✅ Actions performed

Full review triggered.

@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: 2

Caution

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

⚠️ Outside diff range comments (2)
crates/fuse/src/lib.rs (1)

643-648: ⚠️ Potential issue | 🟠 Major

Clear refreshing_metadata on refresh failures too.

This new guard is only released when a PendingRefresh arrives. The spawned refresh tasks currently just log and return on resolve/fetch/decrypt failure, so one transient error leaves that IPNS name stuck in refreshing_metadata and every later lookup/open/readdir skips refresh for that folder until remount.

Please send a failure notification through the refresh channel, or explicitly remove the key on every error path, so retries remain possible.

As per coding guidelines, background tasks must communicate via mpsc channels.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fuse/src/lib.rs` around lines 643 - 648, The code currently only
removes entries from refreshing_metadata when a successful PendingRefresh is
received in drain_refresh_completions, leaving names stuck on task failure;
update the background refresh task(s) that perform resolve/fetch/decrypt to
always send a terminal notification over the same refresh channel (e.g., send a
PendingRefresh or a dedicated failure variant via refresh_tx) when they exit
with an error, or alternatively ensure those error paths explicitly remove the
IPNS key from refreshing_metadata before returning; reference the
refresh_rx/refresh_tx channel, the PendingRefresh type, and the
refreshing_metadata/mutated_folders/publish_queue maps so the
drain_refresh_completions logic can reliably clear the in-progress marker on
both success and failure.
crates/fuse/src/read_ops.rs (1)

217-251: ⚠️ Potential issue | 🟠 Major

Lazy load path has the same refreshing_metadata cleanup gap.

Lines 240, 243, and 246 log errors but don't remove the ipns_name from refreshing_metadata, leaving it orphaned on failures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fuse/src/read_ops.rs` around lines 217 - 251, The lazy-load background
task adds ipns_name into fs.refreshing_metadata but on several error branches
(the Err arms in the rt.spawn async resolving/fetching/decrypting flow) it never
removes it, leaving entries orphaned; modify the async closure spawned by
rt.spawn so that ipns_name is removed from fs.refreshing_metadata on all exit
paths (success and any error) — e.g. capture fs or a clone, and ensure removal
happens in every Err arm or use a small RAII guard/local finally-style helper
inside the async move block that calls fs.refreshing_metadata.remove(&ipns_name)
when dropped, referencing the existing symbols ipns_name,
fs.refreshing_metadata, rt.spawn, and the tx send/resolve/fetch/decrypt branches
so the cleanup always runs.
🧹 Nitpick comments (1)
crates/fuse/src/read_ops.rs (1)

190-214: Consider extracting the duplicate async refresh logic into a shared helper.

The async blocks for needs_refresh (lines 190-214) and needs_load (lines 224-248) are nearly identical—both resolve IPNS, fetch content, decrypt, and send PendingRefresh. Extracting this into a shared function would reduce duplication and simplify applying the refreshing_metadata cleanup fix to both paths.

Also applies to: 224-248

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fuse/src/read_ops.rs` around lines 190 - 214, Duplicate async logic in
the needs_refresh and needs_load spawn blocks should be extracted into a shared
async helper (e.g., fetch_and_send_pending_refresh) that takes the API client
(&api), ipns_name, folder_key, refresh_ino, and the tx sender and performs
resolve_ipns -> fetch_content -> decrypt_metadata_from_ipfs_public ->
tx.send(PendingRefresh) while preserving the same warn/debug log messages on
errors; replace both rt.spawn blocks to call
rt.spawn(fetch_and_send_pending_refresh(...)) so the identical flow is
centralized and you can apply the refreshing_metadata cleanup fix in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/fuse/src/platform/windows/read_ops.rs`:
- Around line 146-175: The async refresh task currently never removes ipns_name
from fs.refreshing_metadata on error, so implement the same failure-reporting
pattern used for content prefetch: add a PendingRefresh::Failure variant to the
PendingRefresh enum (mirroring PendingContent::Failure), and on each error path
inside the spawned task (resolve, fetch, decrypt) send
tx.send(PendingRefresh::Failure { ino: refresh_ino, ipns_name }) instead of just
logging; ensure drain_refresh_completions() (the consumer of refresh_tx) is
updated to handle PendingRefresh::Failure by cleaning up fs.refreshing_metadata
and any other bookkeeping, while leaving the existing success send of
PendingRefresh::Success (or the current PendingRefresh structure with metadata)
intact.

In `@crates/fuse/src/read_ops.rs`:
- Around line 183-215: The async refresh task leaves ipns_name in the
fs.refreshing_metadata set on all error paths (in the resolve/fetch/decrypt
branches), blocking future refreshes; modify the code around the needs_refresh
handling (the rt.spawn block) so the ipns_name is removed from
refreshing_metadata in every outcome—either by cloning a thread-safe handle to
refreshing_metadata (e.g., Arc/Mutex or DashMap) into the task and removing
ipns_name before returning on both success and each Err branch, or by sending a
dedicated cleanup/failure message (via refresh_tx or a new channel) that the
main loop consumes to remove the entry; apply the same pattern to the needs_load
path so load-related tasks also clear refreshing_metadata on error.

---

Outside diff comments:
In `@crates/fuse/src/lib.rs`:
- Around line 643-648: The code currently only removes entries from
refreshing_metadata when a successful PendingRefresh is received in
drain_refresh_completions, leaving names stuck on task failure; update the
background refresh task(s) that perform resolve/fetch/decrypt to always send a
terminal notification over the same refresh channel (e.g., send a PendingRefresh
or a dedicated failure variant via refresh_tx) when they exit with an error, or
alternatively ensure those error paths explicitly remove the IPNS key from
refreshing_metadata before returning; reference the refresh_rx/refresh_tx
channel, the PendingRefresh type, and the
refreshing_metadata/mutated_folders/publish_queue maps so the
drain_refresh_completions logic can reliably clear the in-progress marker on
both success and failure.

In `@crates/fuse/src/read_ops.rs`:
- Around line 217-251: The lazy-load background task adds ipns_name into
fs.refreshing_metadata but on several error branches (the Err arms in the
rt.spawn async resolving/fetching/decrypting flow) it never removes it, leaving
entries orphaned; modify the async closure spawned by rt.spawn so that ipns_name
is removed from fs.refreshing_metadata on all exit paths (success and any error)
— e.g. capture fs or a clone, and ensure removal happens in every Err arm or use
a small RAII guard/local finally-style helper inside the async move block that
calls fs.refreshing_metadata.remove(&ipns_name) when dropped, referencing the
existing symbols ipns_name, fs.refreshing_metadata, rt.spawn, and the tx
send/resolve/fetch/decrypt branches so the cleanup always runs.

---

Nitpick comments:
In `@crates/fuse/src/read_ops.rs`:
- Around line 190-214: Duplicate async logic in the needs_refresh and needs_load
spawn blocks should be extracted into a shared async helper (e.g.,
fetch_and_send_pending_refresh) that takes the API client (&api), ipns_name,
folder_key, refresh_ino, and the tx sender and performs resolve_ipns ->
fetch_content -> decrypt_metadata_from_ipfs_public -> tx.send(PendingRefresh)
while preserving the same warn/debug log messages on errors; replace both
rt.spawn blocks to call rt.spawn(fetch_and_send_pending_refresh(...)) so the
identical flow is centralized and you can apply the refreshing_metadata cleanup
fix in one place.
🪄 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: 38a527d8-456f-4bbb-819b-47de7dd572ac

📥 Commits

Reviewing files that changed from the base of the PR and between 09e6830 and d83249e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • apps/desktop/src-tauri/src/fuse/mod.rs
  • apps/desktop/src-tauri/src/fuse/windows/mod.rs
  • crates/fuse/src/dir_ops.rs
  • crates/fuse/src/lib.rs
  • crates/fuse/src/platform/windows/dir_ops.rs
  • crates/fuse/src/platform/windows/read_ops.rs
  • crates/fuse/src/read_ops.rs
  • tests/desktop-e2e/scripts/run-all.ps1
  • tests/desktop-e2e/scripts/test-cross-client-sync.ps1
  • tests/desktop-e2e/scripts/test-cross-client-sync.sh

Comment thread crates/fuse/src/platform/windows/read_ops.rs Outdated
Comment thread crates/fuse/src/read_ops.rs
PendingRefresh was a plain struct, so error paths in spawned metadata
refresh tasks never sent a message — leaving ipns_name stuck in
refreshing_metadata forever and blocking future refreshes for that
folder.

Convert PendingRefresh to an enum with Success/Failure variants
(matching PendingContent and PendingFilePointer patterns), extract
duplicated spawn logic into spawn_metadata_refresh() helper, and
update drain_refresh_completions() to handle both variants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 8e79f5de316f

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated no new comments.

@FSM1 FSM1 requested a review from Copilot April 14, 2026 00:02
@FSM1 FSM1 merged commit 1e3ef75 into main Apr 14, 2026
32 checks passed
@FSM1 FSM1 deleted the fix/desktop-e2e-cross-client-sync-poll branch April 14, 2026 00:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:cipherbox-fuse:fix Patch version bump (bug fix) for cipherbox-fuse release:desktop:fix Patch version bump (bug fix) for desktop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants