Skip to content

fix: prevent delivered message status from regressing#535

Merged
torlando-tech merged 1 commit intomainfrom
fix/delivered-state-regression-main
Feb 23, 2026
Merged

fix: prevent delivered message status from regressing#535
torlando-tech merged 1 commit intomainfrom
fix/delivered-state-regression-main

Conversation

@torlando-tech
Copy link
Copy Markdown
Owner

Summary

  • LXMF fires spurious failure/sent callbacks after a message is already confirmed delivered, triggering propagation retries that overwrite delivered (double checkmark) with propagated (single checkmark)
  • Python: adds _successfully_delivered tracking set to guard _on_message_delivered, _on_message_failed, and _on_message_sent from regressing already-delivered messages
  • Kotlin: makes delivered truly terminal — blocks any status update that would change it to a non-delivered state (defense-in-depth)

Evidence

Device logs showing the bug and fix working:

16:04:12  _on_message_delivered(): DELIVERED (confirmed by recipient)  ✅
16:04:49  _on_message_failed(): retrying via propagation node          ← spurious, already delivered
16:14:26  _on_message_delivered(): PROPAGATED (stored on relay)        ← overwrites delivered ✗

# After fix:
16:40:24  _on_message_delivered(): DELIVERED (confirmed by recipient)  ✅
16:40:28  Blocking status regression from 'delivered' to 'sent'        ← blocked ✅
16:40:30  Blocking status regression from 'delivered' to 'sent'        ← blocked ✅

Test plan

  • Existing MessageStatusIconTest tests pass
  • Existing MessagingViewModelTest status degradation tests pass
  • New tests: propagated/retrying_propagated/sent status blocked when message already delivered
  • Verified on device — delivered messages stay delivered after spurious callbacks

🤖 Generated with Claude Code

LXMF fires spurious failure/sent callbacks after a message is already
confirmed delivered. This triggered propagation retries that overwrote
'delivered' (double checkmark) with 'propagated' (single checkmark).

Python: add _successfully_delivered tracking set (mirrors existing
_successfully_propagated pattern) to guard _on_message_delivered,
_on_message_failed, and _on_message_sent from regressing state.

Kotlin: make 'delivered' truly terminal — block any status update that
would change it to a non-delivered state (defense-in-depth).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 23, 2026

Greptile Summary

This PR implements a robust fix for LXMF's spurious callback issue where delivered messages regress to propagated/sent status. The solution uses defense-in-depth:

Python layer (reticulum_wrapper.py)

Kotlin layer (MessagingViewModel.kt)

Testing (MessagingViewModelTest.kt)

  • 3 new tests verify blocking of propagated, retrying_propagated, and sent status updates for already-delivered messages
  • Tests confirm no repository calls are made when status regression is blocked

The implementation is clean, well-documented, and addresses the root cause with appropriate logging for debugging.

Confidence Score: 4/5

  • Safe to merge with minimal risk - addresses a real bug with defense-in-depth approach and comprehensive tests
  • Score of 4 (not 5) due to minor thread-safety concern in Python layer where _successfully_delivered dict is accessed from multiple threads without locks. While Python's GIL provides some protection, explicit locking would be more robust. Otherwise, the implementation is solid with thorough testing and clear documentation.
  • Consider adding thread-safety locks to python/reticulum_wrapper.py for the tracking dicts

Important Files Changed

Filename Overview
python/reticulum_wrapper.py Adds _successfully_delivered tracking dict to guard callback handlers from regressing already-delivered messages; includes cleanup in periodic maintenance
app/src/main/java/com/lxmf/messenger/viewmodel/MessagingViewModel.kt Adds defense-in-depth guard to block any status update that would regress delivered to non-delivered states
app/src/test/java/com/lxmf/messenger/viewmodel/MessagingViewModelTest.kt Adds 3 comprehensive tests verifying that propagated, retrying_propagated, and sent statuses are blocked when message is already delivered

Last reviewed commit: 14425c4

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 547 to +548
self._successfully_propagated = {} # {msg_hash_hex: timestamp} - messages that reached relay
self._successfully_delivered = {} # {msg_hash_hex: timestamp} - messages confirmed by recipient
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.

These dicts are accessed from multiple threads (LXMF callbacks + cleanup loop) without locks. Consider using threading.Lock() similar to announce_lock on line 484 to prevent race conditions during concurrent read/modify operations.

Prompt To Fix With AI
This is a comment left during a code review.
Path: python/reticulum_wrapper.py
Line: 547-548

Comment:
These dicts are accessed from multiple threads (LXMF callbacks + cleanup loop) without locks. Consider using `threading.Lock()` similar to `announce_lock` on line 484 to prevent race conditions during concurrent read/modify operations.

How can I resolve this? If you propose a fix, please make it concise.

@torlando-tech torlando-tech merged commit ced6530 into main Feb 23, 2026
13 checks passed
@torlando-tech torlando-tech deleted the fix/delivered-state-regression-main branch February 23, 2026 22:41
@sentry
Copy link
Copy Markdown
Contributor

sentry bot commented Feb 23, 2026

Codecov Report

❌ Patch coverage is 78.94737% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
python/reticulum_wrapper.py 78.94% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

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.

1 participant