Skip to content

Keep the local creation date of a message missing from the state - #6712

Closed
gpunto wants to merge 1 commit into
v6from
fix/v6-preserve-created-locally-at
Closed

gpunto wants to merge 1 commit into
v6from
fix/v6-preserve-created-locally-at

Conversation

@gpunto

@gpunto gpunto commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Goal

A message that fails to send can render above messages that were sent before it. updateState copies
createdLocallyAt from the message already in state, and sets it to null when the state does not hold the
message. A failed send has no createdAt either, so its sort key becomes null and sortedBy places it first.

Closes AND-1547

Implementation

Fall back to the message's own createdLocallyAt instead of dropping it.

getMessageById reads in-memory state only, so the message is missing whenever it is not currently loaded,
which includes the window right after a reconnect replaces the message list.

Testing

The two tests that covered the missing-message case asserted the date was dropped, so they now assert it is
kept. Both fail without the fallback.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gpunto gpunto added the pr:bug Bug fix label Sep 18, 2026
@github-actions

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 ignored for dependabot PRs).

🎉 Great job! This PR is ready for review.

@github-actions

Copy link
Copy Markdown
Contributor

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.26 MB 5.32 MB 0.05 MB 🟢
stream-chat-android-offline 5.49 MB 5.54 MB 0.05 MB 🟢
stream-chat-android-ui-components 10.64 MB 10.76 MB 0.11 MB 🟢
stream-chat-android-compose 12.87 MB 13.15 MB 0.28 MB 🟡

@sonarqubecloud

Copy link
Copy Markdown

@gpunto

gpunto commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

Closing this. The reasoning behind it did not survive scrutiny and I cannot demonstrate a path that reaches the state it guards against.

What I claimed: updateState nulls createdLocallyAt when the message is missing from the state, which for a failed send leaves a null sort key and puts the message at the top of the list. The code does do that, but every route I proposed to oldMessage == null fell apart:

  • logicRegistry.removeChannel only runs for deleted channels, so leaving a chat does not evict the channel logic.
  • Message trimming is off unless an integrator sets channelMessageLimits, which is empty by default.
  • On reconnect onConnectionEstablished runs retryFailedEntities() before restoreActiveChannels(), so the retry result arrives while the message is still in the state.
  • The refresh wipe that would have removed it is fixed by Keep local only messages in the list when a reconnect refreshes the channel #6708, which is merged.

There is also no evidence the state ever occurred. The only message dumps in the report show createdLocallyAt populated at every step. I inferred the null from the rendering order in a screenshot and built the rest on top of that.

A smoke test on the merged v6 produced the exact shape this was about, a permanently failed message with createdAt null, and its createdLocallyAt was intact.

The change is still defensible in isolation, since discarding a date the message already carries is wrong, but merging it implies a user facing bug we cannot show is reachable. Keeping the analysis in AND-1547, which stays open. If a client log ever shows a null createdLocallyAt on a failed send, this is ready to revive.

@gpunto gpunto closed this Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant