Repository navigation
fix(chat): settle a denied send instead of offering a retry - #1760
Merged
Merged
Conversation
The server answers Denied when it won't take a message from this user in this chat, so a retry gets the same answer. Text and photo sends now mark the row DENIED instead of FAILED, and the photo path drops its stored blob as it already does for EncryptionNotAllowed.
… retry A DENIED row now reads "Not Delivered. You can't post in this chat" in the error colour, always shows, and isn't tappable. Photo bubbles stop offering Retry for any failure the send path marked non-retryable; they used to ignore that flag.
Denied carries no reason. If a speaker rule changed, the stored rules are stale and the composer stays up; re-reading the chat lets speakerBlock see the new rules and hide it. A removal from the group still isn't visible, because GetChat doesn't return the caller's membership.
A denied send now settles as DENIED with its own message, and typing and read-pointer callers already ignore the result, so these refusals no longer need to reach Bugsnag. One user produced 6 of the 18 overnight events this way.
ChatEntityMapper.toEntity writes is_member = 1 by default. If the stream's MemberLeft landed before the re-read returned, the upsert put a removed user back in the feed as a member. The re-read now keeps whatever membership is stored.
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.
A
SendMessageDenied left the row FAILED and retryable, so the user saw "Not Delivered. Tap to retry" for a send the server would never take. One user on 2026.10.1 produced 6 of the 18 overnight Bugsnag events this way in about 30 seconds: each Retry came back Denied, the composer stayed up, typing notices kept going to the chat, and every refusal was reported.MessageStatus.DENIEDandDeliveryStatus.DENIED, stored by enum name, so there is no migration. The converter's fallback means an older build reads a DENIED row as SENT. Outbox and sweep queries match'SENDING'or'SENT', so nothing resends a DENIED row, and a late confirm still settles it as SENT.MessagingDelegateandMediaSendDelegatewrite DENIED onSendMessageError.Denied. The photo path drops the stored blob, as it already did forEncryptionNotAllowed.MediaMessageBubblenow honoursMediaSendProgress.Failed.retryable, which it used to ignore, so photos that fail withEncryptionNotAllowedstop offering a retry too.rereadAfterDeniedcallsGetChatand stores the result, so a changed speaker rule hides the composer. It writes throughupsertKeepingMembership: the plain upsert setsis_member = 1, which put a user back in the feed when the stream'sMemberLeftlanded before the re-read returned. The read and the write are separate calls, so aMemberLeftlanding between them can still be overwritten. The re-read runs inline: the row is already DENIED, so the UI doesn't wait, but photos queued behind it start one RPC later.SendMessageError.Denied,NotifyIsTypingError.DeniedandAdvancePointerError.Deniedare nowUnreportedError.A user removed from a group keeps the composer until the stream's
MemberLeftarrives.GetChatdoesn't return the caller's membership andDeniedcarries no reason, so the re-read can't tell a removal from a speaker rule. If the server also refusesGetChatto that user, the re-read'sGetChatError.Deniedis still reported; it's shared with chat hydration and link cards, so it isn't changed here.