Repository navigation
fix: existing messages retry guards - #1982
Merged
Merged
Conversation
isekovanic
requested review from
MartinCupela,
NikolaGoranNaumovski,
oliverlaz and
szuperaz
November 3, 2026 00:42
isekovanic
commented
Nov 3, 2026
oliverlaz
approved these changes
Nov 3, 2026
Contributor
|
Reviewed at
|
Contributor
Author
|
@MartinCupela fixed here |
MartinCupela
approved these changes
Nov 3, 2026
MartinCupela
left a comment
Contributor
There was a problem hiding this comment.
755dcaf6e fixes the stale error on an "already exists" retry. The new assertion fails without the fix and passes with it, and all 4,895 tests and the type check pass.
github-actions Bot
pushed a commit
that referenced
this pull request
Nov 3, 2026
## [10.0.0-rc.31](v10.0.0-rc.30...v10.0.0-rc.31) (2026-11-03) ### ⚠ BREAKING CHANGES * the message composer installs the command middlewares by default: createActiveCommandGuardMiddleware and createCommandStringExtractionMiddleware in the text composer chain, createCommandInjectionMiddleware in the composition chain and createDraftCommandInjectionMiddleware in the draft composition chain. Typing `/giphy cats` activates giphy and leaves `cats` in the text, and a command picked with `textComposer.setCommand()` is sent with its name. Delete your own installs of these middlewares. A UI without a command chip removes all four by id: `stream-io/text-composer/active-command-guard` and `stream-io/text-composer/command-string-extraction` from `textComposer.middlewareExecutor`, `stream-io/message-composer-middleware/command-string-injection` from `compositionMiddlewareExecutor` and `stream-io/message-composer-middleware/draft-command-string-injection` from `draftCompositionMiddlewareExecutor`. See ai-docs/v10-breaking-changes.md#composer-command-middlewares-default. ### Bug Fixes * existing messages retry guards ([#1982](#1982)) ([c7c971f](c7c971f)) * install the command middlewares in the composer by default ([#1985](#1985)) ([1dc25e2](1dc25e2)) * let an edit clear all custom fields ([#1983](#1983)) ([9437b09](9437b09)) * **pagination:** keep loaded items when filterQueryResults empties a later page ([#1986](#1986)) ([78a32c3](78a32c3)) * **search:** match message text by word prefix in MessageSearchSource ([#1987](#1987)) ([ef58ef5](ef58ef5))
|
🎉 This PR is included in version 10.0.0-rc.31 🎉 The release is available on: Your semantic-release bot 📦🚀 |
2 of 6 tasks
isekovanic
added a commit
to GetStream/stream-chat-react-native
that referenced
this pull request
Nov 3, 2026
Tickets: - https://linear.app/stream/issue/REACT-1184 - https://linear.app/stream/issue/REACT-1400 ## 🎯 Goal Bumps `stream-chat` to `10.0.0-rc.31` and removes the SDK code it makes redundant. rc.31 includes GetStream/stream-chat-js#1982, GetStream/stream-chat-js#1985, GetStream/stream-chat-js#1986 and GetStream/stream-chat-js#1987. | Commit | Ticket | Change | | --- | --- | --- | | [`328ecfb7e`](328ecfb) | [REACT-1184](https://linear.app/stream/issue/REACT-1184) | Remove `setupCommandUIMiddlewares` | | [`01671f45b`](01671f4) | [REACT-1400](https://linear.app/stream/issue/REACT-1400) | Pinned-message search uses the default text filter | | [`f34a0b01c`](f34a0b0) | | Bump `stream-chat` to `^10.0.0-rc.31` | ## 🛠 Implementation details ### REACT-1184 - remove `setupCommandUIMiddlewares` Commit: [`328ecfb7e`](328ecfb) GetStream/stream-chat-js#1985 installs the four command middlewares in every composer by default: the active command guard and the command string extraction around the commands middleware, and the command injection after attachments, for messages and for drafts. Those are the same middlewares, with the same ids and positions, that `setupCommandUIMiddlewares` inserted, so the helper has nothing left to do and is removed. SampleApp no longer calls it. This is a breaking change. Apps that call `setupCommandUIMiddlewares` delete the call. Apps that never called it now get the command behaviour too: typing `/giphy cats` makes `giphy` the active command and leaves `cats` in the input. `ai-docs/ai-migration-v9-to-v10.md` and `ai-docs/v10-breaking-changes.md` are updated, and the note about the REACT-1489 fix to this helper is dropped since the helper is gone. ### REACT-1400 - pinned-message search Commit: [`01671f45b`](01671f4) `MessageSearchSource` matched message text by exact equality, so `ChannelPinnedMessageListProvider` replaced that filter with its own `text: { $q }`. GetStream/stream-chat-js#1987 makes the default `text: { $autocomplete }`, so the override is removed. Pinned-message search now matches by word prefix: "lun" finds "Lunch tomorrow?". An empty query still lists every pinned message. ### Bump `stream-chat` to `10.0.0-rc.31` Commit: [`f34a0b01c`](f34a0b0) The SDK and the four example apps move to `^10.0.0-rc.31`. Besides the two changes above, it brings: - GetStream/stream-chat-js#1982 (REACT-1311): a send or retry the server already has is reported as sent instead of failed. - GetStream/stream-chat-js#1986 (REACT-1443): a channel list no longer empties when a later page only has channels another list owns. This only matters for apps that set an ownership resolver on `ChannelManager`. Neither needs a change here. ## 🎨 UI Changes None. ## 🧪 Testing - In SampleApp on iOS, typing `/giphy cats` shows the GIPHY chip with `cats` in the input, and sending it shows the Giphy preview. Picking Giphy from the commands menu and typing `dogs` does the same. - In SampleApp on iOS, pinned-message search finds "offline send check" for "offl" and "sen", and shows no results for "zzq". - The full unit suite, `yarn typecheck`, `yarn lint` and `yarn build` pass. ## ☑️ Checklist - [x] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [x] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ticket: https://linear.app/stream/issue/REACT-1311
CLA
Description of the changes, What, Why and How?
A send can reach the server while its response never reaches the client. The usual cause is the client's 3000ms request timeout, which matches the backend's write timeout, so a slow request can time out on the client after the server has already saved the message. The client then marks the message failed, and every later attempt with the same ID gets "already exists". This was handled for v9 in #1907, which was closed in favour of fixing it in v10. This PR covers every path a send takes in v10.
7e82010b3f403ed09bf9ca86a7ce5c68"Already exists" on send and retry
Commit:
7e82010bMessageOperationStatePolicy.failure()already marked the messagereceivedon "already exists", butMessageOperations.run()rethrew the error anyway. Socomposer.send()returned'failed'and reported "Send message request failed" for a message that had been sent, andretry()rejected, so a resend could never succeed.failure()now returns whether the operation succeeded after all, andrun()only rethrows when it didn't. That's the case only for "already exists" on a send or retry. An edit or a delete rejected with the same code is treated as a failure, as before. A successful send also clears its entry in the failed-send cache.The other half of the ticket, the default update request ignoring a handler's options, was already fixed by #1949.
Server copy before the failure
Commit:
3f403ed0When the server saves the message just before the client's timeout fires, its
message.newcan arrive first and the message shows as sent. The timeout then reachedfailure(), which marked the message failed without checking what was held, so a sent message flipped back to failed.A send or retry now counts as successful when the held message is already
received. Since the optimistic step sets it tosending, areceivedcopy can only come from the server.Replay from the offline queue
Commit:
9bf9ca86With offline support, a send that times out is also queued. When the queue replays it after reconnecting and the server answers "already exists",
executePendingTasksdeleted the task but left the message failed, until a reload or a retry fixed it.QueueableOperationgets anonReplayRejectedhook next toonReplay, andexecutePendingTaskscalls it before deciding whether to keep the task. Thesend-messageoperation uses it: on "already exists", a held message that is stillfailedis upserted intoclient.messageStoreasreceived, which updates every list holding it (the channel and, for a reply, its thread), and is written to the offline DB under the task's channel. A message that isn't failed any more, or isn't loaded, is left alone. The hook runs only for replays, so first attempts keep going through the message-operations layer.Testing
Commit:
a7ce5c68received; an edit rejected with the same code still rejects and is markedfailed; the composer returns'sent'with no notification.receivedcopy and then times out resolves and keeps the server's copy, and the composer returns'sent'.received; another rejection leaves the message failed; a message that's already settled or isn't loaded is left alone; the offline DB gets thereceivedmessage under the task's channel;executePendingTaskshands a rejected replay to the hook.yarn test,yarn run types, eslint and prettier pass.Changelog
message.newarrived.