Skip to content

fix: message being marked as sent before the request completes#39003

Merged
ggazzo merged 2 commits intodevelopfrom
regression-mark-message-sent
Feb 25, 2026
Merged

fix: message being marked as sent before the request completes#39003
ggazzo merged 2 commits intodevelopfrom
regression-mark-message-sent

Conversation

@sampaiodiego
Copy link
Copy Markdown
Member

@sampaiodiego sampaiodiego commented Feb 24, 2026

Proposed changes (including videos or screenshots)

The previous code was running inside a method stub, that means it runs every before the actual request to sendMessage is sent. With this change it is now properly waiting the request to complete to remove the temp property from the message.

The only caveat here is that the previous behavior is still in place which means if we receive the new message via websockets even before the HTTP request finishes, the message will be marked as sent as well (this is not a really problem, just makes things potentially a bit inconsistent). this is caused by this code:

await upsertMessage({ msg, subscription });

Issue(s)

Introduced by #38067

Steps to test or reproduce

Further comments

Summary by CodeRabbit

  • Bug Fixes

    • Prevented premature marking of messages as sent; temporary message state is now cleared after send completion and callback results are propagated for reliable handling.
  • Refactor

    • Improved pre-send processing with early exits for reactions, overly long messages, slash commands, and message-edit flows; message normalization and client-only flags are handled more consistently.
  • Chores

    • Added a patch release entry for this fix.

Task: ARCH-1996

@sampaiodiego sampaiodiego requested a review from a team as a code owner February 24, 2026 18:45
@dionisio-bot
Copy link
Copy Markdown
Contributor

dionisio-bot bot commented Feb 24, 2026

Looks like this PR is ready to merge! 🎉
If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Feb 24, 2026

🦋 Changeset detected

Latest commit: ef5c372

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 41 packages
Name Type
@rocket.chat/meteor Patch
@rocket.chat/core-typings Patch
@rocket.chat/rest-typings Patch
@rocket.chat/uikit-playground Patch
@rocket.chat/api-client Patch
@rocket.chat/apps Patch
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/ddp-client Patch
@rocket.chat/fuselage-ui-kit Patch
@rocket.chat/gazzodown Patch
@rocket.chat/http-router Patch
@rocket.chat/livechat Patch
@rocket.chat/model-typings Patch
@rocket.chat/ui-avatar Patch
@rocket.chat/ui-client Patch
@rocket.chat/ui-contexts Patch
@rocket.chat/ui-voip Patch
@rocket.chat/web-ui-registration Patch
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/presence-service Patch
@rocket.chat/queue-worker Patch
@rocket.chat/abac Patch
@rocket.chat/federation-matrix Patch
@rocket.chat/license Patch
@rocket.chat/media-calls Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/models Patch
@rocket.chat/network-broker Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-video-conf Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch
@rocket.chat/server-fetch Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 24, 2026

Walkthrough

Refactors client message send flow: moves preprocessing (reactions, length, slash commands, editing) to the flow layer, normalizes flags before send, returns callback results from low-level sendMessage, and removes local temporary-message state updates after successful send.

Changes

Cohort / File(s) Summary
Callback Handling
apps/meteor/app/lib/client/methods/sendMessage.ts
Replaced fire-and-forget clientCallbacks.run('afterSaveMessage', ...) with returning its result; removed in-memory update that marked temporary message as sent.
Message Processing Pipeline
apps/meteor/client/lib/chats/flows/sendMessage.ts
Added imports and preprocessing steps: processSetReaction, processTooLongMessage, processSlashCommand, processMessageEditing, and onClientBeforeSendMessage; added early-exit logic and removed client-only e2e/isEditing flags; clear temporary flag from Messages.state after send.
Release Notes
.changeset/nice-penguins-rhyme.md
Added changeset entry for a patch release documenting the fix to marking messages as sent prematurely.

Sequence Diagram

sequenceDiagram
    participant User
    participant Flow as Client Flow
    participant Proc as Processors
    participant SDK as sendMessage SDK
    participant Callbacks as Callbacks
    participant Store as Messages Store

    User->>Flow: send message
    Flow->>Proc: processSetReaction(message)
    alt reaction handled
        Proc-->>Flow: true (exit)
        Flow-->>User: return
    end
    Flow->>Proc: processTooLongMessage(message)
    alt too long
        Proc-->>Flow: true (exit)
        Flow-->>User: return
    end
    Flow->>Proc: processSlashCommand(message)
    alt slash command handled
        Proc-->>Flow: true (exit)
        Flow-->>User: return
    end
    Flow->>Proc: onClientBeforeSendMessage(normalize)
    Proc-->>Flow: normalized message
    Flow->>Proc: processMessageEditing(message)
    alt editing handled
        Proc-->>Flow: true (exit)
        Flow-->>User: return
    end
    Flow->>SDK: sendMessage(message)
    SDK->>Callbacks: clientCallbacks.run('afterSaveMessage', ...)
    Callbacks-->>SDK: result
    SDK-->>Flow: success
    Flow->>Store: remove temporary flag from message
    Flow-->>User: message sent
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through code with tiny paws,

Checked reactions, commands, and length because,
Edits caught and flags made right,
Callbacks returned without a fright,
Temp state cleared — now messages pause no more.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing the timing issue where messages are marked as sent before the HTTP request completes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the 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
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

Copy link
Copy Markdown
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/meteor/client/lib/chats/flows/sendMessage.ts`:
- Around line 40-45: Remove the inline comment above the Messages.state.update
call — the implementation must not contain code comments; locate the block that
calls Messages.state.update (the predicate using record._id === message._id &&
record.temp === true and the updater ({ temp: _, ...record }) => record) and
delete the comment line ("// after the request is complete we can go ahead and
mark as sent") so only the imperative code remains, ensuring no other inline
comments are left in that function.
- Around line 39-45: The temp record is left as temp:true when
sdk.call('sendMessage', message, previewUrls) throws; wrap the call in a
try/catch inside the process() flow and on error remove or mark the local stub
created earlier by using Messages.state.update to target record._id ===
message._id && record.temp === true and either delete it or set temp:false plus
a failure flag; ensure the catch also re-throws or surfaces the error for
existing error handling so UI shows the toast while local state is cleaned up.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55bf071 and a54c020.

📒 Files selected for processing (2)
  • apps/meteor/app/lib/client/methods/sendMessage.ts
  • apps/meteor/client/lib/chats/flows/sendMessage.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/client/lib/chats/flows/sendMessage.ts
  • apps/meteor/app/lib/client/methods/sendMessage.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: ggazzo
Repo: RocketChat/Rocket.Chat PR: 35995
File: apps/meteor/app/api/server/v1/rooms.ts:1107-1112
Timestamp: 2026-02-23T17:53:06.802Z
Learning: In Rocket.Chat PR reviews, maintain strict scope boundaries—when a PR is focused on a specific endpoint (e.g., rooms.favorite), avoid reviewing or suggesting changes to other endpoints that were incidentally refactored (e.g., rooms.invite) unless explicitly requested by maintainers.
📚 Learning: 2026-02-10T16:32:42.586Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38528
File: apps/meteor/client/startup/roles.ts:14-14
Timestamp: 2026-02-10T16:32:42.586Z
Learning: In Rocket.Chat's Meteor client code, DDP streams use EJSON and Date fields arrive as Date objects; do not manually construct new Date() in stream handlers (for example, in sdk.stream()). Only REST API responses return plain JSON where dates are strings, so implement explicit conversion there if needed. Apply this guidance to all TypeScript files under apps/meteor/client to ensure consistent date handling in DDP streams and REST responses.

Applied to files:

  • apps/meteor/client/lib/chats/flows/sendMessage.ts
📚 Learning: 2025-11-19T18:20:37.116Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37419
File: apps/meteor/server/services/media-call/service.ts:141-141
Timestamp: 2025-11-19T18:20:37.116Z
Learning: In apps/meteor/server/services/media-call/service.ts, the sendHistoryMessage method should use call.caller.id or call.createdBy?.id as the message author, not call.transferredBy?.id. Even for transferred calls, the message should appear in the DM between the two users who are calling each other, not sent by the person who transferred the call.

Applied to files:

  • apps/meteor/app/lib/client/methods/sendMessage.ts
📚 Learning: 2026-01-17T01:51:47.764Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38219
File: packages/core-typings/src/cloud/Announcement.ts:5-6
Timestamp: 2026-01-17T01:51:47.764Z
Learning: In packages/core-typings/src/cloud/Announcement.ts, the AnnouncementSchema.createdBy field intentionally overrides IBannerSchema.createdBy (object with _id and optional username) with a string enum ['cloud', 'system'] to match existing runtime behavior. This is documented as technical debt with a FIXME comment at apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts:53 and should not be flagged as an error until the runtime behavior is corrected.

Applied to files:

  • apps/meteor/app/lib/client/methods/sendMessage.ts
📚 Learning: 2026-02-20T09:04:55.725Z
Learnt from: Shreyas2004wagh
Repo: RocketChat/Rocket.Chat PR: 38681
File: apps/meteor/server/modules/streamer/streamer.module.ts:307-313
Timestamp: 2026-02-20T09:04:55.725Z
Learning: In apps/meteor/server/modules/streamer/streamer.module.ts, the catch block in sendToManySubscriptions intentionally uses SystemLogger.debug (not error or warn) for per-subscription delivery failures to keep logs less noisy, as this was a deliberate design choice reviewed and approved by KevLehman.

Applied to files:

  • apps/meteor/app/lib/client/methods/sendMessage.ts
🔇 Additional comments (2)
apps/meteor/client/lib/chats/flows/sendMessage.ts (1)

3-9: LGTM — imports are correctly placed and all consumed.

apps/meteor/app/lib/client/methods/sendMessage.ts (1)

45-48: Verify that afterSaveMessage handlers tolerate messages with temp: true.

The afterSaveMessage callback now runs while the message still carries temp: true. Specifically, the analytics event tracker logs all messages as "sent" regardless of confirmation status, and thread scroll managers may operate on unconfirmed messages. Ensure registered handlers either filter temp messages or handle them safely.

Comment on lines 39 to +45
await sdk.call('sendMessage', message, previewUrls);

// after the request is complete we can go ahead and mark as sent
Messages.state.update(
(record) => record._id === message._id && record.temp === true,
({ temp: _, ...record }) => record,
);
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Feb 24, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Temp record is never cleaned up when sdk.call throws.

The Meteor method stub (in sendMessage.ts) stores the message with temp: true before the DDP round-trip. If sdk.call rejects (network error, server error, rate-limit, etc.) the exception propagates out of process(), the Messages.state.update is skipped, and the record remains stuck with temp: true in local state until a page reload — the error toast is shown but the message continues to appear as "sending" with no failure indication.

Consider cleaning up the orphaned record in an error branch, e.g.:

💡 Suggested approach
-	await sdk.call('sendMessage', message, previewUrls);
-
-	Messages.state.update(
-		(record) => record._id === message._id && record.temp === true,
-		({ temp: _, ...record }) => record,
-	);
+	try {
+		await sdk.call('sendMessage', message, previewUrls);
+		Messages.state.update(
+			(record) => record._id === message._id && record.temp === true,
+			({ temp: _, ...record }) => record,
+		);
+	} catch (error) {
+		Messages.state.delete(message._id);
+		throw error;
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await sdk.call('sendMessage', message, previewUrls);
// after the request is complete we can go ahead and mark as sent
Messages.state.update(
(record) => record._id === message._id && record.temp === true,
({ temp: _, ...record }) => record,
);
try {
await sdk.call('sendMessage', message, previewUrls);
// after the request is complete we can go ahead and mark as sent
Messages.state.update(
(record) => record._id === message._id && record.temp === true,
({ temp: _, ...record }) => record,
);
} catch (error) {
Messages.state.delete(message._id);
throw error;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/lib/chats/flows/sendMessage.ts` around lines 39 - 45, The
temp record is left as temp:true when sdk.call('sendMessage', message,
previewUrls) throws; wrap the call in a try/catch inside the process() flow and
on error remove or mark the local stub created earlier by using
Messages.state.update to target record._id === message._id && record.temp ===
true and either delete it or set temp:false plus a failure flag; ensure the
catch also re-throws or surfaces the error for existing error handling so UI
shows the toast while local state is cleaned up.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

in case sdk.call throws it is ok to keep as temp: true, we'd actually to add a new UI to handle a retry scenario

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@sampaiodiego Got it! Keeping temp: true on failure makes sense for a retry UI. That will provide a much better user experience than losing the message. Thank you for clarifying the intent!


✏️ Learnings added
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 39003
File: apps/meteor/client/lib/chats/flows/sendMessage.ts:39-45
Timestamp: 2026-02-24T19:16:32.051Z
Learning: In apps/meteor/client/lib/chats/flows/sendMessage.ts, when sdk.call('sendMessage', ...) throws an error, the message is intentionally left with temp: true (not deleted or cleaned up) to support a future retry UI feature. This allows users to retry sending failed messages rather than losing them.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 32703
File: apps/meteor/client/lib/chats/flows/uploadFiles.ts:52-58
Timestamp: 2026-02-12T15:39:28.416Z
Learning: In `apps/meteor/client/lib/chats/flows/uploadFiles.ts`, when E2E encryption is required but not allowed (e.g., `E2E_Enable_Encrypt_Files` setting is disabled), the function intentionally abandons the entire upload queue and displays a toast error. This fail-fast behavior prevents partial uploads when encryption requirements cannot be met and is the expected behavior, not a bug.

Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38219
File: packages/core-typings/src/cloud/Announcement.ts:5-6
Timestamp: 2026-01-17T01:51:47.764Z
Learning: In packages/core-typings/src/cloud/Announcement.ts, the AnnouncementSchema.createdBy field intentionally overrides IBannerSchema.createdBy (object with _id and optional username) with a string enum ['cloud', 'system'] to match existing runtime behavior. This is documented as technical debt with a FIXME comment at apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts:53 and should not be flagged as an error until the runtime behavior is corrected.

Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37419
File: apps/meteor/server/services/media-call/service.ts:141-141
Timestamp: 2025-11-19T18:20:37.116Z
Learning: In apps/meteor/server/services/media-call/service.ts, the sendHistoryMessage method should use call.caller.id or call.createdBy?.id as the message author, not call.transferredBy?.id. Even for transferred calls, the message should appear in the DM between the two users who are calling each other, not sent by the person who transferred the call.

Learnt from: Shreyas2004wagh
Repo: RocketChat/Rocket.Chat PR: 38681
File: apps/meteor/server/modules/streamer/streamer.module.ts:307-313
Timestamp: 2026-02-20T09:04:55.725Z
Learning: In apps/meteor/server/modules/streamer/streamer.module.ts, the catch block in sendToManySubscriptions intentionally uses SystemLogger.debug (not error or warn) for per-subscription delivery failures to keep logs less noisy, as this was a deliberate design choice reviewed and approved by KevLehman.

Learnt from: ggazzo
Repo: RocketChat/Rocket.Chat PR: 35995
File: apps/meteor/app/api/server/v1/rooms.ts:1107-1112
Timestamp: 2026-02-23T17:53:06.802Z
Learning: In Rocket.Chat PR reviews, maintain strict scope boundaries—when a PR is focused on a specific endpoint (e.g., rooms.favorite), avoid reviewing or suggesting changes to other endpoints that were incidentally refactored (e.g., rooms.invite) unless explicitly requested by maintainers.

Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38528
File: apps/meteor/client/startup/roles.ts:14-14
Timestamp: 2026-02-10T16:32:42.586Z
Learning: In Rocket.Chat's Meteor client code, DDP streams use EJSON and Date fields arrive as Date objects; do not manually construct new Date() in stream handlers (for example, in sdk.stream()). Only REST API responses return plain JSON where dates are strings, so implement explicit conversion there if needed. Apply this guidance to all TypeScript files under apps/meteor/client to ensure consistent date handling in DDP streams and REST responses.

@codecov
Copy link
Copy Markdown

codecov bot commented Feb 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.64%. Comparing base (4c0244d) to head (ef5c372).
⚠️ Report is 16 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #39003      +/-   ##
===========================================
- Coverage    70.65%   70.64%   -0.01%     
===========================================
  Files         3189     3189              
  Lines       112716   112712       -4     
  Branches     20456    20386      -70     
===========================================
- Hits         79635    79623      -12     
- Misses       31033    31039       +6     
- Partials      2048     2050       +2     
Flag Coverage Δ
unit 71.18% <ø> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sampaiodiego sampaiodiego changed the title regression: properly mark msg as sent only after request completes fix: mark msg as sent before the request completes Feb 24, 2026
@sampaiodiego sampaiodiego changed the title fix: mark msg as sent before the request completes fix: message being marked as sent before the request completes Feb 24, 2026
@sampaiodiego sampaiodiego force-pushed the regression-mark-message-sent branch from 235aa09 to ef5c372 Compare February 24, 2026 19:40
@ggazzo ggazzo added this to the 8.3.0 milestone Feb 25, 2026
@ggazzo ggazzo added the stat: QA assured Means it has been tested and approved by a company insider label Feb 25, 2026
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Feb 25, 2026
@dionisio-bot dionisio-bot bot enabled auto-merge February 25, 2026 04:52
@ggazzo
Copy link
Copy Markdown
Member

ggazzo commented Feb 25, 2026

/jira ARCH-1935

@ggazzo ggazzo disabled auto-merge February 25, 2026 04:53
@dionisio-bot dionisio-bot bot enabled auto-merge February 25, 2026 04:53
@ggazzo ggazzo merged commit f6dce5f into develop Feb 25, 2026
47 of 49 checks passed
@ggazzo ggazzo deleted the regression-mark-message-sent branch February 25, 2026 04:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants