Skip to content

fix(reticulum): PN Sync races, Auto Ignore, deposit/have-id hardening - #835

Merged
rinchen merged 7 commits into
mainfrom
huh
Aug 10, 2026
Merged

fix(reticulum): PN Sync races, Auto Ignore, deposit/have-id hardening#835
rinchen merged 7 commits into
mainfrom
huh

Conversation

@rinchen

@rinchen rinchen commented Aug 10, 2026

Copy link
Copy Markdown
Member

Problem

Propagation Node (PN) Sync/Auto was unreliable in several ways that looked like “empty inbox” or a stuck UI even when the stack was busy or racey:

  1. Stuck /get / permanent busy — Cancel mid-retrieve left the client download in a non-Idle state (RETRIEVE_BUSY), so later Sync attempts failed until restart. Client have-ids were not persisted atomically, so a crash mid-write could wipe haves and trigger re-fetch storms.
  2. False “synced” / false empties — Auto could treat remotes that only soft-deferred (retrieve already in flight) plus a local-prop settle as full success, advancing lastPropagationSyncAt and suppressing retries. local-prop Sync could return Ok/100% when the RNS stack was not live. Late local-prop responses could clear a newer Sync attempt’s error/idle state.
  3. Host silent /get racing user Sync — Finishing Host peer/periodic retrieve cleared propagation_sync_target unconditionally, wiping a newer user Sync latch and breaking progress/cancel ownership.
  4. Bad discovered PNs poisoned Auto — Auto kept picking poorly behaved discovered nodes for sync and deposit; Manual could still work, but Auto looked broken with no way to exclude a node without deleting/configuring it.
  5. Deposit / path hardening gaps — In-process deposit validated stamps at min_cost = 0 and could self-Link on lock miss; absurd hop counts ranked ghosts ahead of real peers; docs still described Sync as /offer-primary while code is /get-primary.

How this fixes it

  • Cancel → Idle — Abort client download transfer on cancel; second Sync can start again. Persist client have-ids with tmp+rename; warn on corrupt JSON.
  • Honest Sync/Auto outcomes — Soft-defer codes (RETRIEVE_BUSY, outbound busy, stack not live) clear error without backoff success; Auto does not advance success timestamps when remotes only deferred and only local settled. local-prop returns PROPAGATION_STACK_NOT_LIVE when live is missing. startSync ignores superseded attempt tokens.
  • Latch ownership — Silent Host /get terminal clear only if propagation_sync_target still equals that peer (same guard on user Sync busy/terminal paths).
  • Ignore for Auto — Sidecar-persisted Auto blacklist with Network Ignore/Allow; filters Auto sync and deposit, Manual unchanged.
  • Deposit / ranking / docs — Validate at real min_stamp_cost; defer instead of self-Link on lock miss; demote absurd hops (aligned Rust/TS); rewrite Sync//get-primary docs; locale refresh for stale EN + false friends; RNCP cold-start hydrate skip with upsert_failed retry token.

Test plan

  • Manual Sync to a remote PN while Host silent /get is finishing — Sync latch and progress stay on the user target
  • Sync local-prop before sidecar live attach — expect soft-defer / stack-not-live, not 100% success
  • Auto with remotes all retrieve-busy + local settle — do not advance lastPropagationSyncAt; UI shows retrieve-busy
  • Ignore a discovered PN for Auto, confirm it is skipped for sync/deposit; Allow restores it
  • Cancel mid-/get then Sync again — second start succeeds (not permanent RETRIEVE_BUSY)
  • pnpm run reticulum:sidecar:test and related Vitest (propagation store/Auto/effective/Section/RNCP)

Summary by CodeRabbit

  • New Features

    • Added controls to ignore or re-enable propagation nodes during automatic syncing.
    • Added persistence and management for ignored propagation destinations.
    • Improved automatic node selection using hop quality and blacklist preferences.
    • Added offline MeshCore node public-key short IDs in the node list.
  • Bug Fixes

    • Improved propagation sync cancellation, retries, fallback behavior, and error reporting.
    • Prevented stale or overlapping sync attempts from overwriting current results.
    • Reduced duplicate control actions after reconnects or application restarts.
  • Documentation

    • Expanded propagation setup, troubleshooting, API, and common-issue guidance.

rinchen added 4 commits August 9, 2026 17:28
…ties

Persist client /get have-ids, abort mid-transfer cancels, keep Host enabled
for local settle, demote absurd hop ghosts, and skip known RNCP control LXMF.
Add a sidecar-persisted Auto blacklist with Network UI Ignore/Allow
controls so poorly behaved nodes are skipped without blocking Manual.
Silent Host /get no longer clears a newer user Sync latch; local-prop and Auto
soft-defer paths stop reporting false success; stamp validation, atomic have-ids,
locale/docs, and behavioral tests cover the audit findings.
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rinchen, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 7 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

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.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 5c6a383e-0d20-45c9-9eb5-d9fa218e4e74

📥 Commits

Reviewing files that changed from the base of the PR and between 0d35b93 and 16bfb88.

📒 Files selected for processing (10)
  • reticulum-sidecar/src/stack/live.rs
  • reticulum-sidecar/src/stack/lxmf_outbound.rs
  • reticulum-sidecar/src/stack/persistence.rs
  • reticulum-sidecar/src/stack/propagation_bridge.rs
  • src/renderer/lib/reticulum/reticulumPropagationAutoApply.test.ts
  • src/renderer/lib/reticulum/reticulumPropagationAutoApply.ts
  • src/renderer/lib/reticulum/reticulumPropagationMode.test.ts
  • src/renderer/lib/reticulum/reticulumPropagationMode.ts
  • src/renderer/stores/reticulumPropagationStore.test.ts
  • src/renderer/stores/reticulumPropagationStore.ts
📝 Walkthrough

Walkthrough

The PR adds persistent Auto-mode propagation blacklists, blacklist-aware target selection, stricter hop and path handling, cancellable client retrieval, stale-sync protection, explicit local deposit outcomes, RNCP retry controls, API updates, UI controls, and supporting documentation and validation.

Changes

Reticulum propagation and runtime updates

Layer / File(s) Summary
Auto-blacklist persistence and API
reticulum-sidecar/src/api/..., reticulum-sidecar/src/stack/persistence.rs, reticulum-sidecar/src/stack/mod.rs, src/renderer/stores/reticulumPropagationStore.ts
Auto-blacklisted destination hashes are validated, capped, persisted, exposed through the sidecar, and managed by renderer store actions.
Blacklist-aware candidate selection
reticulum-sidecar/src/stack/pn_cascade.rs, src/renderer/lib/reticulum/..., src/renderer/components/ReticulumPropagation*.tsx
Auto mode excludes blacklisted destinations, ranks plausible hops first, treats hops above 32 as unknown, and exposes ignore or allow controls.
Client retrieval and sync lifecycle
reticulum-sidecar/src/stack/live.rs, reticulum-sidecar/src/stack/propagation_bridge.rs, src/renderer/stores/reticulumPropagationStore.ts, src/renderer/lib/reticulum/...
Client /get retrieval uses shared path validation, persisted have-IDs, transfer aborts, soft-defer classification, unique attempt stamps, and stale-result protection.
Local propagation deposit outcomes
reticulum-sidecar/src/stack/lxmf_outbound.rs
Local deposits now report completed, busy, or failed outcomes and requeue or advance the cascade without self-Link fallback.
Runtime, validation, and documentation support
src/renderer/runtime/..., src/renderer/lib/rncpLxmfControlSideEffectDedup.ts, scripts/..., docs/...
Hydrated RNCP messages use one-shot retry tokens. Overlay application, locale checks, accessibility expectations, troubleshooting guidance, and propagation contracts are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UI
  participant PropagationStore
  participant SidecarAPI
  participant PropagationBridge
  participant ReticulumNode
  UI->>PropagationStore: select target and start sync
  PropagationStore->>SidecarAPI: POST /api/v1/propagation/sync
  SidecarAPI->>PropagationBridge: start client /get
  PropagationBridge->>ReticulumNode: validate path and retrieve messages
  ReticulumNode-->>PropagationBridge: progress or error
  PropagationBridge-->>PropagationStore: completion, deferred, or failure
  PropagationStore-->>UI: update sync state
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main Reticulum changes: PN Sync race fixes, Auto Ignore support, and deposit/have-id hardening.
Docstring Coverage ✅ Passed Docstring coverage is 89.90% 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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch huh

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.

rinchen and others added 2 commits August 9, 2026 18:20
…blacklist

CI coverage failed because the reconnect-hardening contract still expected the
pre-blacklist call shape for shouldApplyLinkDeliveryTimeoutFailureBridge.
Build & Test failed solely on scripts/install-actionlint.mjs network fetch;
no product code change.

Co-authored-by: Joey Stanford <rinchen@users.noreply.github.com>

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

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

Caution

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

⚠️ Outside diff range comments (2)
reticulum-sidecar/src/stack/propagation_bridge.rs (1)

562-592: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not mark undecodable blobs as "have" — this permanently loses them.

poll_client_download's Complete branch computes tids from every raw blob in blobs (line 568-571) and immediately calls client.add_local_message and, below, merge_persist_client_have_ids for all of them — before messages is built from the decode-filtered subset (line 583-586). A blob that fails to decode (corrupted transfer, unexpected content) is still marked "have" and persisted.

Because have-ids gate re-download across every PN ("Auto cascading across PNs reports haves instead of re-downloading"), an undecodable blob can never be retried from this PN or any other PN once its transient ID is recorded here. Since it was also never decoded into a message, it never reaches Chat. The message is lost silently and permanently.

drain_local_inbox (lines 671-680) handles the equivalent local case correctly: it derives have/purge IDs only from the successfully decoded messages, with an explicit comment that undecodable blobs must stay for a later retry. poll_client_download should follow the same ordering.

As per path instructions (AGENTS.md): "preserve state on failures and log meaningful failure points" and "ensure competing or stale operations cannot overwrite newer state."

🛠️ Proposed fix: derive have-ids from decoded messages, not raw blobs
             PropagationClientState::Complete => {
                 let listed = client.available_messages().len();
                 let downloaded = client.received_count();
                 let blobs = client.take_received_messages();
-                // Remember retrieved tids as haves (survives acknowledge/cleanup)
-                // so the next `/get` (same or other PN) purges instead of re-serving.
-                let tids: Vec<PropagationTransientId> = blobs
-                    .iter()
-                    .map(|blob| LxMessage::compute_propagation_transient_id(blob))
-                    .collect();
+                let messages = blobs
+                    .iter()
+                    .filter_map(|blob| decode_downloaded_propagated_blob(&self.identity, blob))
+                    .collect::<Vec<_>>();
+                // Only remember successfully-decoded tids as haves. An undecodable
+                // blob must stay re-fetchable (from this or another PN) — never
+                // decoded, never delivered, so it must not be silently dropped.
+                let tids: Vec<PropagationTransientId> =
+                    messages.iter().filter_map(|msg| msg.transient_id).collect();
                 for tid in &tids {
                     client.add_local_message(*tid);
                 }
                 let have_added = tids.len();
                 // Consume the terminal snapshot → Idle so the next
                 // start_client_download can proceed without a cancel first.
                 let _ = client.acknowledge_transfer();
                 drop(client);
                 if have_added > 0 {
                     merge_persist_client_have_ids(&self.client_have_path, &tids);
                 }
-                let messages = blobs
-                    .iter()
-                    .filter_map(|blob| decode_downloaded_propagated_blob(&self.identity, blob))
-                    .collect::<Vec<_>>();
                 ClientDownloadPoll::Complete {
                     messages,
                     listed,
                     downloaded,
                 }
             }
🤖 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 `@reticulum-sidecar/src/stack/propagation_bridge.rs` around lines 562 - 592,
Update the PropagationClientState::Complete branch in poll_client_download so
have IDs are derived only from successfully decoded messages, matching
drain_local_inbox behavior. Decode blobs before calling client.add_local_message
or merge_persist_client_have_ids, retain undecodable blobs for retry, and
preserve the existing listed/downloaded reporting and completion flow.

Source: Path instructions

src/renderer/lib/reticulum/reticulumPropagationMode.ts (1)

159-174: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Normalize implausible configured-node hop counts before sorting.

node.hops values above 32 still sort before unknown hops in this function. The sidecar maps these values to unknown with hops_rank. Auto Sync can therefore select a different configured node than outbound Auto deposit.

Map non-finite or out-of-range configured hops to Number.POSITIVE_INFINITY before sortByHopsThenKey. Add coverage for a configured node with hops above 32.

Proposed fix
   const rows: { id: string; hops: number; sortKey: string }[] = [];
   for (const node of nodes) {
     if (node.id === 'local-prop' || !node.enabled) continue;
     if (isPropagationHashAutoBlacklisted(node.destination_hash, autoBlacklist)) continue;
+    const hops = node.hops ?? Number.POSITIVE_INFINITY;
     rows.push({
       id: node.id,
-      hops: node.hops ?? Number.POSITIVE_INFINITY,
+      hops: hasFinitePropagationHops(hops) ? hops : Number.POSITIVE_INFINITY,
       sortKey: node.name,
     });
   }
🤖 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 `@src/renderer/lib/reticulum/reticulumPropagationMode.ts` around lines 159 -
174, Update listConfiguredRemotePropagationIds to normalize each node’s hops
before sorting: convert non-finite values and values outside the valid 0–32
range to Number.POSITIVE_INFINITY, while preserving valid hop counts. Add
coverage for a configured node with hops above 32 and verify it sorts as an
unknown-hop node.
🟡 Other comments (1)
reticulum-sidecar/src/stack/persistence.rs-425-436 (1)

425-436: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Tighten Auto-blacklist hash validation to reject, not strip, invalid characters.

normalize_propagation_auto_blacklist_hash filters out every character that is not an ASCII hex digit, then checks the remaining length. A hash string with stray characters (a typo, a copy-paste artifact, anything other than the expected 32 hex digits) can be silently reduced to a 32-character value that passes validation, instead of being rejected.

Other hash-normalization helpers in this file reject malformed input instead of stripping it: add_propagation_node requires hash.chars().all(is_ascii_hexdigit) after trim().to_lowercase(), and the RRC helpers strip only the known : separator with replace(':', ""). This helper should follow the same pattern so a corrupted destination hash cannot silently pass as valid and blacklist (or un-blacklist) the wrong node.

This helper backs both add_propagation_auto_blacklist / remove_propagation_auto_blacklist and the deserialization path (line 992), so the leniency affects every entry point for this field.

As per path instructions (AGENTS.md): "Validate external hashes and inputs, use standardized error/result handling."

🛡️ Proposed fix: reject non-hex input instead of stripping it
     pub fn normalize_propagation_auto_blacklist_hash(raw: &str) -> Result<String, String> {
-        let clean: String = raw
-            .chars()
-            .filter(char::is_ascii_hexdigit)
-            .collect::<String>()
-            .to_lowercase();
-        if clean.len() != 32 {
+        let clean = raw.trim().to_lowercase();
+        if clean.len() != 32 || !clean.chars().all(|c| c.is_ascii_hexdigit()) {
             return Err("destination_hash must be 32 hex characters".into());
         }
         Ok(clean)
     }
🤖 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 `@reticulum-sidecar/src/stack/persistence.rs` around lines 425 - 436, Update
normalize_propagation_auto_blacklist_hash to trim and lowercase the input, then
reject it unless all characters are ASCII hexadecimal and the normalized value
is exactly 32 characters; do not filter or strip arbitrary characters. Preserve
the existing Result error behavior so add_propagation_auto_blacklist,
remove_propagation_auto_blacklist, and deserialization consistently reject
malformed hashes.

Source: Path instructions

🧹 Nitpick comments (2)
reticulum-sidecar/src/stack/persistence.rs (1)

1181-1202: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a test for the 256-entry cap.

The existing test covers normalization, idempotent re-add, and invalid-hash rejection, but not the "propagation Auto blacklist is full" error path when PROPAGATION_AUTO_BLACKLIST_CAP is reached. A short test filling the list to the cap and asserting the next add fails would cover that boundary.

🤖 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 `@reticulum-sidecar/src/stack/persistence.rs` around lines 1181 - 1202, Extend
propagation_auto_blacklist_add_remove_normalizes_hash or add a focused test for
add_propagation_auto_blacklist that inserts PROPAGATION_AUTO_BLACKLIST_CAP
distinct valid hashes, verifies the list reaches the cap, and asserts the next
valid add returns an error for the full-list boundary.
reticulum-sidecar/src/stack/propagation_bridge.rs (1)

737-778: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Serialze persisted have-ids writes across concurrent drains.

merge_persist_client_have_ids reads the current have-ids file, merges IDs, and writes back. It can run from poll_client_download and from drain_local_inbox inside a spawned drain task, while the client-download driver is still active. If both paths start with the same on-disk state, the later write can drop the earlier new IDs. Keep a Mutex<()> for client_have_path and hold it across the load-merge-atomic-write sequence.

🤖 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 `@reticulum-sidecar/src/stack/propagation_bridge.rs` around lines 737 - 778,
The merge_persist_client_have_ids flow must serialize concurrent persisted
have-ID updates. Add or reuse a Mutex<()> associated with client_have_path,
acquire its guard at the start of merge_persist_client_have_ids, and hold it
through load_client_have_ids, merging, and the atomic temporary-file
write/rename sequence so concurrent poll_client_download and drain_local_inbox
calls cannot overwrite each other’s IDs.

Source: Path instructions

🤖 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 `@reticulum-sidecar/src/stack/live.rs`:
- Around line 3783-3795: In the path-gate failure branch of
start_propagation_sync, only clear propagation_sync_target when the current
target still belongs to this attempt, matching the ownership checks used later
in the same function. Compare driver.propagation_sync_target() with this
attempt’s destination hash before calling set_propagation_sync_target(None),
while preserving the existing logging and error return.

In `@reticulum-sidecar/src/stack/lxmf_outbound.rs`:
- Around line 677-700: Move the `mark_propagated_delivery_attempt` call and its
maximum-attempts check out of the pre-branch flow and place them immediately
before `start_packed_delivery`, so `InProcessDepositOutcome::Busy` requeues
without consuming an attempt. Add a behavioral regression test that holds the
local-node mutex across multiple ticks, then releases it and verifies the local
deposit succeeds.
- Around line 606-628: Update the PropagationNode try_lock handling in the
outbound delivery paths around local cost calculation and the related lines near
782 and 810 to distinguish WouldBlock from Poisoned. Keep WouldBlock on the
existing busy/requeue path, but for Poisoned log the mutex failure, return
Failed without treating it as Busy, and advance to the next PN cascade.

In `@src/renderer/lib/reticulum/reticulumPropagationAutoApply.ts`:
- Around line 63-66: Update the sync error propagation around startSync,
attemptSync, and finishWith so soft-deferred failures do not overwrite the last
failed attempt’s lastSyncError. Preserve the prior error when handling
PROPAGATION_STACK_NOT_LIVE, RNS stack not live, or PROPAGATION_RETRIEVE_BUSY,
and ensure finishWith clears to the last active sync attempt’s preserved error.

In `@src/renderer/stores/reticulumPropagationStore.ts`:
- Around line 429-435: Update the propagation sync request flow around
proxyPost() and its catch handler to verify stillCurrent() immediately after
completion and before clearing the shared stall watchdog; stale successful
responses must return 'deferred' rather than 'accepted', and stale failures or
rejections must not clear the newer attempt’s watchdog. Add coverage in
reticulumPropagationStore.test.ts for stale remote-success and stale rejection
cases.

---

Outside diff comments:
In `@reticulum-sidecar/src/stack/propagation_bridge.rs`:
- Around line 562-592: Update the PropagationClientState::Complete branch in
poll_client_download so have IDs are derived only from successfully decoded
messages, matching drain_local_inbox behavior. Decode blobs before calling
client.add_local_message or merge_persist_client_have_ids, retain undecodable
blobs for retry, and preserve the existing listed/downloaded reporting and
completion flow.

In `@src/renderer/lib/reticulum/reticulumPropagationMode.ts`:
- Around line 159-174: Update listConfiguredRemotePropagationIds to normalize
each node’s hops before sorting: convert non-finite values and values outside
the valid 0–32 range to Number.POSITIVE_INFINITY, while preserving valid hop
counts. Add coverage for a configured node with hops above 32 and verify it
sorts as an unknown-hop node.

---

Other comments:
In `@reticulum-sidecar/src/stack/persistence.rs`:
- Around line 425-436: Update normalize_propagation_auto_blacklist_hash to trim
and lowercase the input, then reject it unless all characters are ASCII
hexadecimal and the normalized value is exactly 32 characters; do not filter or
strip arbitrary characters. Preserve the existing Result error behavior so
add_propagation_auto_blacklist, remove_propagation_auto_blacklist, and
deserialization consistently reject malformed hashes.

---

Nitpick comments:
In `@reticulum-sidecar/src/stack/persistence.rs`:
- Around line 1181-1202: Extend
propagation_auto_blacklist_add_remove_normalizes_hash or add a focused test for
add_propagation_auto_blacklist that inserts PROPAGATION_AUTO_BLACKLIST_CAP
distinct valid hashes, verifies the list reaches the cap, and asserts the next
valid add returns an error for the full-list boundary.

In `@reticulum-sidecar/src/stack/propagation_bridge.rs`:
- Around line 737-778: The merge_persist_client_have_ids flow must serialize
concurrent persisted have-ID updates. Add or reuse a Mutex<()> associated with
client_have_path, acquire its guard at the start of
merge_persist_client_have_ids, and hold it through load_client_have_ids,
merging, and the atomic temporary-file write/rename sequence so concurrent
poll_client_download and drain_local_inbox calls cannot overwrite each other’s
IDs.
🪄 Autofix

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: QUIET

Plan: Pro Plus

Run ID: 6e448464-5b61-433b-8035-7f72729d9a3d

📥 Commits

Reviewing files that changed from the base of the PR and between 8d0be83 and 0d35b93.

⛔ Files ignored due to path filters (18)
  • reticulum-sidecar/patches/README.md is excluded by !reticulum-sidecar/patches/**
  • reticulum-sidecar/patches/rsLXMF-propagation-client-abort-transfer.patch is excluded by !reticulum-sidecar/patches/**
  • src/renderer/locales/cs/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/de/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/en/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/es/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/fr/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/id/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/it/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/ja/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/ko/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/nl/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/pl/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/pt-BR/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/ru/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/tr/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/uk/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/zh/translation.json is excluded by !src/renderer/locales/**
📒 Files selected for processing (41)
  • docs/agents/common-issues.md
  • docs/agents/reticulum.md
  • docs/meshcore-meshtastic-parity.md
  • docs/reticulum-sidecar-ipc.md
  • docs/reticulum.md
  • docs/troubleshooting.md
  • reticulum-sidecar/src/api/mod.rs
  • reticulum-sidecar/src/api/propagation.rs
  • reticulum-sidecar/src/stack/live.rs
  • reticulum-sidecar/src/stack/lxmf_outbound.rs
  • reticulum-sidecar/src/stack/mod.rs
  • reticulum-sidecar/src/stack/persistence.rs
  • reticulum-sidecar/src/stack/pn_cascade.rs
  • reticulum-sidecar/src/stack/propagation_bridge.rs
  • reticulum-sidecar/src/stack/propagation_mode.rs
  • scripts/apply-rsLXMF-propagation-client-abort-transfer.sh
  • scripts/check-i18n-quality.mjs
  • scripts/lib/ratspeak-overlay-apply-list.sh
  • scripts/update.sh
  • src/renderer/components/NodeListPanel.test.tsx
  • src/renderer/components/ReticulumPropagationNotice.tsx
  • src/renderer/components/ReticulumPropagationSection.test.tsx
  • src/renderer/components/ReticulumPropagationSection.tsx
  • src/renderer/lib/reticulum/reticulumDiagnosticSnapshot.ts
  • src/renderer/lib/reticulum/reticulumOutboundFailureBridge.ts
  • src/renderer/lib/reticulum/reticulumPropagationAutoApply.test.ts
  • src/renderer/lib/reticulum/reticulumPropagationAutoApply.ts
  • src/renderer/lib/reticulum/reticulumPropagationEffective.test.ts
  • src/renderer/lib/reticulum/reticulumPropagationEffective.ts
  • src/renderer/lib/reticulum/reticulumPropagationMode.test.ts
  • src/renderer/lib/reticulum/reticulumPropagationMode.ts
  • src/renderer/lib/reticulum/reticulumPropagationSync.test.ts
  • src/renderer/lib/reticulum/reticulumPropagationSync.ts
  • src/renderer/lib/reticulum/useReticulumPropagationAutoSync.ts
  • src/renderer/lib/rncpLxmfControlSideEffectDedup.test.ts
  • src/renderer/lib/rncpLxmfControlSideEffectDedup.ts
  • src/renderer/runtime/useReticulumRuntime.reconnect-hardening.test.ts
  • src/renderer/runtime/useReticulumRuntime.rncp-receive-dest.test.ts
  • src/renderer/runtime/useReticulumRuntime.ts
  • src/renderer/stores/reticulumPropagationStore.test.ts
  • src/renderer/stores/reticulumPropagationStore.ts

Comment thread reticulum-sidecar/src/stack/live.rs
Comment thread reticulum-sidecar/src/stack/lxmf_outbound.rs Outdated
Comment thread reticulum-sidecar/src/stack/lxmf_outbound.rs
Comment thread src/renderer/lib/reticulum/reticulumPropagationAutoApply.ts
Comment thread src/renderer/stores/reticulumPropagationStore.ts
Guard path-gate latch clear by attempt ownership; do not burn delivery
attempts on local-prop Busy; distinguish poisoned mutex from WouldBlock;
persist have-ids only for decoded mail under a serialize lock; harden
blacklist hash validation; preserve cascade errors across soft-defer; and
rank absurd configured hops as unknown.
@rinchen
rinchen merged commit 1494cc3 into main Aug 10, 2026
21 checks passed
@rinchen
rinchen deleted the huh branch August 10, 2026 01:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants