Skip to content

Conversation

@deepak0x
Copy link
Contributor

@deepak0x deepak0x commented Feb 4, 2026

This PR adds a offline/sync flow to ensure messages don’t remain stuck after app restarts, crashes, or network changes.

  • Stuck TEMP messages: On login, messages that have been in TEMP state for more than 5 minutes are reconciled:

    • If the message exists on the server (via chat.getMessage), its status is updated to SENT.
    • Otherwise, the message is resent. If resend fails, it is moved to ERROR.
  • Auto-retry ERROR messages: When the app reconnects (METEOR.SUCCESS), all messages in ERROR state are automatically retried so users don’t need to manually tap Resend after going back online.

  • ** Details**:

    • Introduced messageSync.ts with reconcileTempMessages() and retryErrorMessages().
    • Exported changeMessageStatus from sendMessage.ts for reuse.
    • TEMP reconciliation runs once on login success.
    • Added a messageSync saga that retries ERROR messages on connection success when the user is logged in.

Issue(s)

Closes #6928, #4471

  • Messages stuck in TEMP remained in a perpetual Sending… state after app crash or force kill.
  • Messages in ERROR state were never retried automatically after network reconnection.

How to Test / Reproduce

Stuck TEMP Messages

  1. Open a room and send a message.
  2. Force close the app while the message is still in Sending… state.
  3. Reopen the app and navigate back to the room.

Expected result:

  • The message is reconciled:

    • Marked as SENT if it exists on the server, or
    • Resent automatically, or
    • Moved to ERROR if resend fails.

Auto-retry ERROR Messages

  1. Enable Airplane Mode.
  2. Send a message (it moves to ERROR).
  3. Disable Airplane Mode and wait for reconnection.

result:

  • The message is retried automatically without tapping Resend.

Screenshots/Videos

WhatsApp.Video.2026-02-10.at.04.06.18.mp4

Types of Changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update

Checklist

  • I have read the CONTRIBUTING document
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Summary by CodeRabbit

  • New Features
    • TEMP messages older than 5 minutes are automatically reconciled with server state.
    • Messages that previously errored are retried automatically.
    • Message synchronization runs in the background after login and when the app reconnects, reducing stuck or failed sends.
    • Per-attempt retry attempts and outcomes are logged to aid visibility and recovery.

…etry ERROR on reconnect

- Export changeMessageStatus from sendMessage for reconciliation
- Add messageSync.ts: reconcileTempMessages (TEMP older than 5min) and retryErrorMessages
- Run reconcileTempMessages on login success (handleLoginSuccess)
- Add messageSync saga: retry ERROR messages on METEOR.SUCCESS when user logged in
- Register messageSync saga in root
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

Walkthrough

Adds message reconciliation and retry utilities and integrates them into sagas: new reconcileTempMessages() and retryErrorMessages() in app/lib/methods/messageSync.ts, exports changeMessageStatus from app/lib/methods/sendMessage.ts, and wires sagas to run reconciliation after login and retry on METEOR.SUCCESS.

Changes

Cohort / File(s) Summary
Message Sync Library
app/lib/methods/messageSync.ts
Adds reconcileTempMessages() and retryErrorMessages(), TEMP_RECONCILIATION_THRESHOLD_MS, helpers (hasMessageNotFoundHint, shouldResendAfterLookupFailure), sequential per-record processing, and extensive error logging.
Send Message Export
app/lib/methods/sendMessage.ts
Makes existing changeMessageStatus exported (export const changeMessageStatus = ...) so other modules (messageSync) can update message statuses.
Saga Wiring
app/sagas/messageSync.js, app/sagas/login.js, app/sagas/index.js
Adds messageSync saga to root; new reconcileTempMessagesSaga forked after login success; retryErrorMessagesSaga triggered on METEOR.SUCCESS.

Sequence Diagram

sequenceDiagram
    actor User
    participant Sagas as "Sagas / Redux"
    participant LocalDB as "Local DB (messages)"
    participant Server as "Server API"

    User->>Sagas: Login Success
    activate Sagas
    Sagas->>LocalDB: reconcileTempMessages()
    activate LocalDB
    LocalDB->>LocalDB: Query TEMP messages older than 5min
    loop per TEMP message
        LocalDB->>Server: getSingleMessage(id)
        alt Server returns message (exists)
            Server-->>LocalDB: message found
            LocalDB->>LocalDB: changeMessageStatus(id, SENT)
        else Server not found / lookup error
            Server-->>LocalDB: not-found / error
            LocalDB->>Server: resendMessage()
            Server-->>LocalDB: resend result (success/fail)
        end
    end
    deactivate LocalDB
    deactivate Sagas

    User->>Sagas: METEOR.SUCCESS
    activate Sagas
    Sagas->>LocalDB: retryErrorMessages()
    activate LocalDB
    LocalDB->>LocalDB: Query ERROR messages
    loop per ERROR message
        LocalDB->>Server: resendMessage()
        Server-->>LocalDB: resend result (success/fail)
    end
    deactivate LocalDB
    deactivate Sagas
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through queues both TEMP and ERROR-lit,
I peered at old messages, one by one they fit,
If server showed them sent, I marked them so,
If not, I nudged a resend and watched them go,
Little rabbit syncs, now no message sits.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 change: implementing offline sync queue with TEMP message reconciliation on restart and auto-retry of ERROR messages.
Linked Issues check ✅ Passed The PR implements all core requirements: reconciles TEMP messages older than 5 minutes on login (#6928), auto-retries ERROR messages on reconnection (#6928), and addresses message reliability issues across restarts and connectivity changes.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the offline sync queue feature: new messageSync utilities, saga integration for retry logic, and exporting changeMessageStatus for reuse.

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


No actionable comments were generated in the recent review. 🎉

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a178a3a and 3b16c27.

📒 Files selected for processing (1)
  • app/lib/methods/messageSync.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/lib/methods/messageSync.ts

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@app/lib/methods/messageSync.ts`:
- Around line 33-49: The outer catch around getSingleMessage treats any error as
"message missing" and triggers resendMessage, which can create duplicates on
transient failures; update the error handling in the try/catch that surrounds
getSingleMessage (and the subsequent changeMessageStatus call) to inspect the
thrown error (e.g. check err.response?.status, err.status, or err.code) and only
call resendMessage(record, record.tmid ?? undefined) when the error indicates
the message is truly not found (e.g. 404 or specific "not found" code); for
other errors (network timeouts, 5xx, auth failures) either rethrow or log and
abort without resending so resendMessage is not invoked erroneously. Ensure you
reference getSingleMessage, changeMessageStatus, resendMessage, messagesStatus
and preserve the inner catch(e) { log(e) } behavior for the actual resend path.
🧹 Nitpick comments (2)
app/sagas/messageSync.js (1)

21-23: Consider using takeLatest instead of takeEvery to prevent concurrent retries.

METEOR.SUCCESS can fire multiple times during reconnection scenarios. Using takeEvery could spawn multiple concurrent retryErrorMessagesSaga instances, potentially causing the same ERROR messages to be retried simultaneously, leading to duplicate sends or race conditions.

♻️ Proposed fix
-const root = function* root() {
-	yield takeEvery(METEOR.SUCCESS, retryErrorMessagesSaga);
-};
+import { call, select, takeLatest } from 'redux-saga/effects';
+
+const root = function* root() {
+	yield takeLatest(METEOR.SUCCESS, retryErrorMessagesSaga);
+};
app/sagas/login.js (1)

251-251: Note: Reconciliation may be cancelled after 2 seconds.

The handleLoginSuccess task is cancelled after a 2-second timeout (see Lines 426-430). If reconcileTempMessages is processing many stuck messages, it may be interrupted mid-execution. This follows the existing pattern for other forked tasks, but consider whether the reconciliation should complete independently.

If uninterrupted execution is important, consider spawning instead of forking:

💡 Alternative using spawn for detached execution
-		yield fork(reconcileTempMessagesSaga);
+		yield spawn(reconcileTempMessagesSaga);

Note: spawn creates a detached task that won't be cancelled when the parent is cancelled. You'd need to import spawn from redux-saga/effects.

@deepak0x deepak0x marked this pull request as draft February 4, 2026 14:04
@diegolmello
Copy link
Member

@deepak0x This is awesome! There's this old issue about the same thing, so add to the Closes list #4471 (comment)

What's missing here, since the PR is draft?

@deepak0x
Copy link
Contributor Author

deepak0x commented Feb 9, 2026

@deepak0x This is awesome! There's this old issue about the same thing, so add to the Closes list #4471 (comment)

What's missing here, since the PR is draft?

hey @diegolmello
actually have to few final testing from my side to ensure it works perfectly
then will add the video in description and make this pr ready
will do this shortly asap...
thanks

Only trigger TEMP-message resend when lookup errors indicate message-not-found, preventing duplicate sends during transient network/auth/server failures.

Co-authored-by: Cursor <[email protected]>
@deepak0x deepak0x marked this pull request as ready for review February 9, 2026 22:40
@deepak0x
Copy link
Contributor Author

deepak0x commented Feb 9, 2026

hey @diegolmello
the pr is ready
Pls review it...
Thanks

Removes branch-added inline/doc comments while preserving behavior, and keeps sequential processing via a helper to satisfy lint rules.

Co-authored-by: Cursor <[email protected]>
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.

feature: Messages stuck in TEMP/ERROR status indefinitely without auto-retry after app restart/network recovery

2 participants