Skip to content

fix(xmtp_mls): propagate retryable errors from own self-publish reaction handlers#3758

Open
Jr-kenny wants to merge 2 commits into
xmtp:mainfrom
Jr-kenny:fix-3747-self-publish-error-propagation
Open

fix(xmtp_mls): propagate retryable errors from own self-publish reaction handlers#3758
Jr-kenny wants to merge 2 commits into
xmtp:mainfrom
Jr-kenny:fix-3747-self-publish-error-propagation

Conversation

@Jr-kenny

@Jr-kenny Jr-kenny commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Closes #3747

Problem

When you do something in a group like leaving it, or deleting one of your own messages, your app first publishes that action to the network, then gets it back and runs a little followup step locally to update your status. Something like "I want to leave the group" (process_own_leave_request_message) and "I deleted a message" (process_own_delete_message).

Those two followups had a bad habit: if something went wrong while they ran, they just quietly threw the error away. One will catch the failure and only write a simple debug line, the other will use a coding shortcut that treats "the database read failed" exactly like "nothing found" and just returns as if all was well.

The problem is that some of these failures are actually temporary, like the database was briefly busy or a little connection shake. Everywhere else in the sync code, a temporary failure causes the system to roll back and try again. However, on these two specific paths, a temporary failure during a leave or delete silently drops. The operation incorrectly appears successful when in fact it failed.

Fix

Both handlers now return Result and pass database read errors up with ? instead of hiding them. A missing row or a different content type is still a normal early return, since those cases are genuinely fine.

A new resolve_own_reaction helper in process_own_message decides what to do with a failure:

  • retryable: forward it, so the sync transaction rolls back and the message is retried, same as the rest of the processing path.
  • non-retryable: log it at warn! with the group and message ids and move on, because the message is already published and retrying won't help.

The reasoning is documented in comments at the call site so the intent is clear to the next reader.

Testing

Added test_own_reaction_handlers_surface_retryable_errors. It injects a retryable storage error into both handlers and checks two things: the handlers themselves return the error, and the error survives the full sync path. The end-to-end part publishes a real message, makes the reaction fail, and asserts that the sync reports the failure in its summary without counting the message as processed. It then clears the fault and confirms the same message is processed cleanly on the next sync, proving the rollback left the cursor in the right place.

Existing test_self_removal* and delete-message tests still pass, and clippy and rustfmt are clean.

Note

Propagate retryable errors from own leave/delete reaction handlers in MlsGroup

  • process_own_leave_request_message and process_own_delete_message now return Result<(), GroupMessageProcessingError> instead of swallowing errors, so database read failures can trigger sync transaction rollback and retry.
  • A new resolve_own_reaction helper in mls_sync.rs maps handler results: retryable errors become IntentResolutionError (triggering rollback), non-retryable errors are logged at warn and skipped, and missing rows are treated as benign no-ops.
  • A fault-injection flag TEST_MODE_OWN_REACTION_RETRYABLE_ERROR is added in test_mocks_helpers.rs with a regression test that verifies retryable errors surface correctly and that clearing the fault allows a subsequent sync to succeed.
  • Behavioral Change: own-reaction failures that were previously silently ignored will now cause the sync transaction to roll back and be retried if the error is retryable.

Macroscope summarized 66fdd7f.

…ion handlers

On the self-publish path, process_own_leave_request_message and
process_own_delete_message discarded their errors: the leave handler matched the
result and dropped Err to a debug! line, and both used let Ok(..) else { return }
reads that swallowed DB failures. Both are called from process_own_message, which
returns a Result, so a retryable storage failure on a client's own leave/delete
reaction was silently dropped instead of rolling the sync back and retrying like
the rest of process_message_inner.

Both handlers now return Result<(), GroupMessageProcessingError> and propagate
read failures with ?. The new resolve_own_reaction helper in process_own_message
branches on is_retryable(): retryable errors propagate so the sync rolls back and
retries, non-retryable errors are logged at warn! and swallowed because the
message is already published and the local reaction is best-effort. The
content_type and missing-row guards stay as benign early returns.

Adds a test that injects a retryable storage error into both handlers and asserts
they surface it rather than silently succeeding.

Closes xmtp#3747
@Jr-kenny Jr-kenny requested a review from a team as a code owner June 10, 2026 21:04
@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.21488% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.46%. Comparing base (8ad390a) to head (66fdd7f).

Files with missing lines Patch % Lines
crates/xmtp_mls/src/groups/mls_sync.rs 93.45% 7 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #3758    +/-   ##
========================================
  Coverage   84.45%   84.46%            
========================================
  Files         408      408            
  Lines       59772    59876   +104     
========================================
+ Hits        50483    50574    +91     
- Misses       9289     9302    +13     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 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.

The fault injection toggles an env-var flag and wasm targets cannot set
environment variables, so the test panics before it can run. Gate it with
cfg(not(target_arch = "wasm32")), same as the other test_mocks_helpers
based tests.
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.

Swallowed errors in process_own_* self-publish reaction handlers (mls_sync.rs)

1 participant