Skip to content

Treat the duplicate message id send error as a successful delivery - #6587

Merged
andremion merged 4 commits into
developfrom
andrerego/and-1320-message-list-crashes-with-a-duplicate-lazycolumn-key-when-a
Jul 28, 2026
Merged

Treat the duplicate message id send error as a successful delivery#6587
andremion merged 4 commits into
developfrom
andrerego/and-1320-message-list-crashes-with-a-duplicate-lazycolumn-key-when-a

Conversation

@andremion

@andremion andremion commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Goal

When a message send is retried after its response was lost (for example, the connection drops before the ack is processed), the backend rejects the retry with a 400 input error: code 4, "a message with ID x already exists". The message was in fact delivered by the first attempt, but the SDK marked it as FAILED_PERMANENTLY. The user then sees a delivered message as failed, and a manual resend fails again with the same error, so the message can never leave the failed state. The iOS SDK already treats this rejection as a successful send.

This is the SDK side of the fix for the message list crash found in the e2e nightly (test_userAddsReactionWhileOffline): the mock server used to accept the duplicate send and store a second copy with the same id, and the channel query response then crashed the Compose message list with a duplicate LazyColumn key. The mock server now mirrors the backend rejection (see GetStream/stream-chat-test-mock-server#51), which makes this SDK path reachable in e2e.

Resolves AND-1320

Implementation

  • Added an internal Error.isDuplicateMessageError() extension that detects the backend rejection: Error.NetworkError with server error code 4 (ChatErrorCode.VALIDATION_ERROR) and a message containing "already exists". This is the same detection the iOS SDK uses in MessageRepository.
  • SendMessageListenerState and SendMessageListenerDatabase now route this failure to their existing success handling, so the message is stored with SyncStatus.COMPLETED instead of FAILED_PERMANENTLY.

Testing

Unit tests: new cases in SendMessageListenerStateTest and SendMessageListenerDatabaseTest cover the duplicate rejection (message marked as completed) and a different validation error (message still marked as failed permanently).

Manual steps:

  1. Open a channel and send a message, then cut connectivity immediately so the send response is lost (on an emulator: adb shell svc wifi disable and adb shell svc data disable right after tapping send). The message must stay in the pending state.
  2. Restore connectivity and wait for the reconnect sync to retry the send.
  3. Before this change, the retried message could show the failed indicator even though the first attempt delivered it. After this change, it shows as sent.

E2e: test_userAddsReactionWhileOffline exercises this flow when the send races the offline switch, together with the mock server PR above. Verified locally against the updated mock server (test passes; the duplicate send returns 400 and the message list contains the message once).

Summary by CodeRabbit

  • Bug Fixes

    • Duplicate message-send responses are now treated as successful, preventing messages from being incorrectly marked as permanently failed.
    • Messages affected by duplicate IDs are stored with a completed sync status.
    • Other validation errors continue to be handled as permanent failures.
  • Tests

    • Added coverage for duplicate message IDs and other validation-error scenarios.

…ivery

The backend rejects a message send whose id already exists with a 400
input error (code 4). This happens when a send is retried after its
response was lost, for example on a connection drop before the ack was
processed. The message was in fact delivered, so marking it as
FAILED_PERMANENTLY shows a delivered message as failed and invites a
resend loop that can never succeed. Mark it as COMPLETED instead, in
line with the iOS SDK behavior.
@andremion andremion added the pr:bug Bug fix label Jul 24, 2026
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled, or the PR is bot-authored.
  • An issue is linked (Linear ticket or GitHub issue), or the PR is bot-authored.

🎉 Great job! This PR is ready for review.

@andremion

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds duplicate-message error classification and routes matching send failures through successful completion in both offline database and state listeners, with tests covering duplicate and other validation errors.

Changes

Duplicate message handling

Layer / File(s) Summary
Duplicate error classification
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/errors/ErrorExtensions.kt
Adds an internal check for network validation errors whose message contains "already exists".
Send result routing and validation
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/.../SendMessageListener*.kt, stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/.../SendMessageListener*Test.kt
Routes duplicate-message failures through success handling and verifies COMPLETED versus FAILED_PERMANENTLY persistence outcomes.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SendMessageListener
  participant ErrorClassifier
  participant SendHandler
  SendMessageListener->>ErrorClassifier: classify failed send error
  ErrorClassifier-->>SendMessageListener: duplicate or non-duplicate
  SendMessageListener->>SendHandler: use success or failure handling
Loading

Poem

I’m a rabbit with messages bright,
Duplicate IDs now land just right.
Success paws stamp “completed” with cheer,
Other failures stay crystal clear.
Hop, hop—validated paths appear!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed Concise and accurately summarizes the main fix: duplicate message ID send errors are treated as successful deliveries.
Description check ✅ Passed Includes Goal, Implementation, and Testing, covering the required substance; only non-critical template sections like UI Changes, Checklist, and GIF are missing.
✨ 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 andrerego/and-1320-message-list-crashes-with-a-duplicate-lazycolumn-key-when-a

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.

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.95 MB 5.95 MB 0.00 MB 🟢
stream-chat-android-ui-components 11.22 MB 11.22 MB 0.00 MB 🟢
stream-chat-android-compose 12.70 MB 12.70 MB 0.00 MB 🟢

@andremion
andremion marked this pull request as ready for review July 24, 2026 11:21
@andremion
andremion requested a review from a team as a code owner July 24, 2026 11:21
@andremion
andremion enabled auto-merge (squash) July 24, 2026 13:01
@sonarqubecloud

Copy link
Copy Markdown

@andremion
andremion merged commit 0650aef into develop Jul 28, 2026
19 checks passed
@andremion
andremion deleted the andrerego/and-1320-message-list-crashes-with-a-duplicate-lazycolumn-key-when-a branch July 28, 2026 12:39
@stream-public-bot stream-public-bot added the released Included in a release label Jul 30, 2026
@stream-public-bot

Copy link
Copy Markdown
Contributor

🚀 Available in v7.7.0

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

Labels

pr:bug Bug fix released Included in a release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants