Skip to content

feat: Adding scheduled messages#40310

Open
Polinarr60 wants to merge 8 commits intoRocketChat:developfrom
moevm:feat/scheduled-messages
Open

feat: Adding scheduled messages#40310
Polinarr60 wants to merge 8 commits intoRocketChat:developfrom
moevm:feat/scheduled-messages

Conversation

@Polinarr60
Copy link
Copy Markdown

@Polinarr60 Polinarr60 commented Apr 25, 2026

Proposed changes (including videos or screenshots)

Added a complete message scheduling feature, enabling users to schedule messages to be sent at a specific date and time.
image
image
image

VIDEO: https://drive.google.com/file/d/1VPe8fiTi2k1KOQu4tJi4f7pGP1mW3C5x/view?usp=sharing

Issue(s)

Cancel #40309

Steps to test or reproduce

  1. Schedule a Message
  • Open any room in Rocket.Chat
  • Type a message and click the clock icon to open the scheduling menu
  • Select "Schedule new message"
  • A modal should appear with date and time pickers
  • Select a future date and time
  • Click "Schedule" button
  • Wait fot the sekected date and time to see your message. It appears without oage reload.
  1. View Scheduled Messages
  • In the same room, click the clock icon again
  • Select "View scheduled messages"
  • A modal should display all your scheduled messages for this room. You can cancel a scheduled message if you want

Further comments

Summary by CodeRabbit

  • New Features

    • Schedule messages for future delivery, cancel scheduled messages, and list paginated scheduled items per room.
    • Composer adds a scheduling control, a schedule dialog, and a “View scheduled messages” action to manage pending sends.
    • Background processing automatically delivers scheduled messages and retries or marks failures as needed.
    • Success and error toasts notify users of scheduling, delivery, cancellation, and validation outcomes.
  • Documentation

    • Added UI text/translations for scheduling flows, input validation, statuses, and empty states.

@Polinarr60 Polinarr60 requested review from a team as code owners April 25, 2026 13:43
@dionisio-bot
Copy link
Copy Markdown
Contributor

dionisio-bot Bot commented Apr 25, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 25, 2026

🦋 Changeset detected

Latest commit: 762aba9

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

This PR includes changesets to release 43 packages
Name Type
@rocket.chat/models Minor
@rocket.chat/meteor Minor
@rocket.chat/ui-composer Minor
@rocket.chat/i18n Minor
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch
@rocket.chat/server-fetch Patch
@rocket.chat/ui-client Major
@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/media-calls Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-contexts Major
@rocket.chat/web-ui-registration Major
@rocket.chat/network-broker Patch
@rocket.chat/fuselage-ui-kit Major
@rocket.chat/ui-voip Major
@rocket.chat/uikit-playground Patch
@rocket.chat/gazzodown Major
@rocket.chat/livechat Patch
@rocket.chat/ui-avatar Major
@rocket.chat/ui-video-conf Major
@rocket.chat/core-typings Minor
@rocket.chat/rest-typings Minor
@rocket.chat/api-client Patch
@rocket.chat/apps Patch
@rocket.chat/ddp-client Patch
@rocket.chat/http-router Patch
@rocket.chat/model-typings Patch
@rocket.chat/license Patch
@rocket.chat/pdf-worker 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 Apr 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds end-to-end scheduled messages: client UI to schedule/view/cancel, REST endpoints, server cron to dispatch and retry scheduled messages, model and typing updates to mark/filter scheduled messages, and English i18n entries.

Changes

Cohort / File(s) Summary
Release notes
.changeset/rare-llamas-behave.md, .changeset/tasty-hoops-try.md
Adds changesets documenting a minor bump and notes for the scheduled messages feature.
Server API
apps/meteor/app/api/server/index.ts, apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
Registers three v1 endpoints: chat.scheduleMessage, chat.getScheduledMessages, chat.cancelScheduledMessage with request validation and permission checks and AJV schemas.
Client — scheduling flows & util
apps/meteor/client/lib/chats/flows/scheduleMessage.ts, apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx, apps/meteor/client/views/room/composer/messageBox/hooks/useScheduleMessageButton.tsx, apps/meteor/client/views/room/composer/messageBox/MessageBoxScheduleModal.tsx
Adds schedule helper, integrates scheduling UI into composer, provides scheduling modal and wiring to API with toasts and composer clearing.
Client — view/delete scheduled messages UI
apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar.tsx, apps/meteor/client/views/room/composer/messageBox/hooks/useViewScheduledMessagesAction.tsx, apps/meteor/client/views/room/composer/messageBox/MessageBoxScheduleMenu.tsx, apps/meteor/client/views/room/composer/messageBox/MessageBoxScheduledMessagesModal.tsx
Adds menu action, hook, schedule menu items, and modal to list and cancel scheduled messages with API calls, toasts, and reload behavior.
Client — schedule button/menu items
apps/meteor/client/views/room/composer/messageBox/MessageBoxScheduleMenu.tsx, apps/meteor/client/views/room/composer/messageBox/hooks/useScheduleMessageButton.tsx
Defines schedule menu items and a hook returning the schedule menu and behavior to open modals and call schedule API.
Server — processing cron
apps/meteor/server/cron/scheduledMessages.ts, apps/meteor/server/startup/cron.ts
Adds cron job that claims, authorizes, sends, retries (with exponential backoff and retry cap), and cleans up scheduled messages; registers job to run every minute.
Types & REST typings
packages/core-typings/src/IMessage/IMessage.ts, packages/rest-typings/src/v1/chat.ts, packages/rest-typings/src/v1/chat/scheduled.ts
Adds scheduled/scheduledAt to IMessage; adds endpoint types and AJV validators for schedule/list/cancel routes and updates ChatEndpoints.
Models / Visibility
packages/models/src/models/Messages.ts
Updates message visibility query builders to exclude documents where scheduled: true.
Translations
packages/i18n/src/locales/en.i18n.json
Adds English i18n keys for scheduling UI, validation messages, statuses, and empty states.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client as Client (UI)
    participant API as Server API
    participant DB as Database
    participant Cron as Cron Job

    rect rgba(100, 150, 255, 0.5)
    Note over User,Client: Schedule a message
    User->>Client: Enter text & choose schedule
    Client->>Client: Validate future datetime
    Client->>API: POST /v1/chat.scheduleMessage
    API->>API: Validate & authorize
    API->>DB: Insert message with scheduled=true, scheduledAt
    DB-->>API: Acknowledge
    API-->>Client: Return created message
    Client->>User: Show success toast
    end

    rect rgba(100, 200, 100, 0.5)
    Note over Cron,DB: Cron processes scheduled messages
    Cron->>DB: Query scheduled=true and scheduledAt <= now
    DB-->>Cron: Return candidates
    loop per message
        Cron->>DB: Attempt atomic claim (set scheduled=false)
        DB-->>Cron: Claim result
        Cron->>API: Load sender & check canSendMessage
        API-->>Cron: Authorization result
        Cron->>API: sendMessage for delivery
        API->>DB: Persist sent message
        Cron->>DB: Delete scheduled record (if send succeeded)
    end
    end

    rect rgba(255, 150, 100, 0.5)
    Note over User,Client: View & cancel scheduled messages
    User->>Client: Open "View scheduled messages"
    Client->>API: GET /v1/chat.getScheduledMessages
    API->>DB: Query scheduled messages for room (paginated)
    DB-->>API: Return results
    API-->>Client: Return messages + counts
    Client->>User: Display list
    User->>Client: Request cancel
    Client->>API: POST /v1/chat.cancelScheduledMessage
    API->>DB: Delete scheduled message
    DB-->>API: Deleted
    API-->>Client: Return success
    Client->>User: Show success toast & refresh
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels: type: feature

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Adding scheduled messages' directly and clearly summarizes the main feature being added across the entire changeset—complete message scheduling functionality.
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.
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.

@coderabbitai coderabbitai Bot added the type: feature Pull requests that introduces new feature label Apr 25, 2026
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: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/models/src/models/Messages.ts (1)

84-106: ⚠️ Potential issue | 🔴 Critical

Several "visible" query methods still surface scheduled (unsent) messages — drift vs the methods you updated.

You added scheduled: { $ne: true } to six find variants, but multiple sibling methods that participate in the same "visible" semantics were not updated. This causes user-visible inconsistencies and at least one count/find pagination drift:

  • countVisibleByRoomIdBetweenTimestampsNotContainingTypes (Lines 980–1014) — its findVisibleByRoomIdBetweenTimestampsNotContainingTypes sibling now excludes scheduled messages, but this counter does not. Anything paginating with count then iterating with find will see an off-by-N total.
  • findVisibleByMentionAndRoomId / findPaginatedVisibleByMentionAndRoomId (Lines 84–106) — a user mentioned inside someone else's scheduled draft will see the mention in their list before the message is actually sent. Privacy/UX leak.
  • findVisibleByIds (Lines 772–781) — direct id lookups still return scheduled messages.
  • findVisibleThreadByThreadId (Lines 783–793) — scheduled replies leak into thread views.
  • getLastVisibleUserMessageSentByRoomId (Lines 1058–1073) — can return a future-dated scheduled message as "last visible," which then drives edit-last-message and similar flows.
  • countVisibleByRoomIdBetweenTimestampsInclusive (Line 681) — same count drift class as above.
  • findForUpdates (Line 860), countPinned/findPaginatedPinnedByRoom/countStarred/findStarredByUserAtRoom — lower risk but still drift from the new "visible excludes scheduled" invariant.

Recommend extracting the visibility predicate ({ _hidden: { $ne: true }, scheduled: { $ne: true } }) into a helper and applying it consistently across every "visible*" / "countVisible*" method, especially the count↔find pairs.

Also applies to: 772-793, 980-1014, 1058-1073

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/models/src/models/Messages.ts` around lines 84 - 106, The
visible-query methods are leaking scheduled (unsent) messages; update
findVisibleByMentionAndRoomId and findPaginatedVisibleByMentionAndRoomId to
include scheduled: { $ne: true } in their query, and factor the common
visibility predicate into a helper (e.g., getVisibleMessageFilter or
VISIBLE_MESSAGE_FILTER) that returns { _hidden: { $ne: true }, scheduled: { $ne:
true } } so you can reuse it; then apply that helper consistently to all
"visible*" and "countVisible*" methods called out in the review
(findVisibleByIds, findVisibleThreadByThreadId,
getLastVisibleUserMessageSentByRoomId,
countVisibleByRoomIdBetweenTimestampsNotContainingTypes,
countVisibleByRoomIdBetweenTimestampsInclusive, findForUpdates,
countPinned/findPaginatedPinnedByRoom, countStarred/findStarredByUserAtRoom) to
eliminate count/find drift and mention/thread leaks.
apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx (1)

502-515: ⚠️ Potential issue | 🟠 Major

Schedule menu should be disabled or hidden when uploading, processing, or recording — not just gated by canSend.

The scheduleMenu is correctly gated by canSend at the parent level (line 502), so it won't render when the user lacks send permission. However, it lacks gating for isRecording, isUploading, and isProcessingUploads states. The Send button disables on all three, but the schedule menu remains clickable while uploads or recording are in progress. Either pass these states to useScheduleMessageButton so the hook can disable itself (via a disabled prop on GenericMenu), or conditionally render the menu only when !isRecording && !isUploading && !isProcessingUploads.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx` around
lines 502 - 515, The scheduleMenu is rendered when canSend is true but is not
disabled during isRecording, isUploading, or isProcessingUploads; update either
the call to useScheduleMessageButton (pass a disabled prop derived from
isRecording || isUploading || isProcessingUploads) so the hook sets
GenericMenu's disabled state, or wrap scheduleMenu with an additional
conditional render so it only renders when canSend && !isRecording &&
!isUploading && !isProcessingUploads; locate usages of scheduleMenu and the hook
useScheduleMessageButton in MessageBox.tsx and ensure the GenericMenu produced
by the hook respects the disabled flag.
🧹 Nitpick comments (7)
packages/models/src/models/Messages.ts (1)

46-82: Add an index to support the scheduled-messages cron.

apps/meteor/server/cron/scheduledMessages.ts polls every minute with Messages.find({ scheduled: true, scheduledAt: { $lte: now } }). None of the indexes declared here cover scheduled or scheduledAt, so this becomes a recurring full-collection scan on the largest collection in the system.

📈 Suggested index
 			{ key: { 'federation.eventId': 1 }, sparse: true },
 			{ key: { t: 1 }, sparse: true },
+			{ key: { scheduledAt: 1 }, sparse: true, partialFilterExpression: { scheduled: true } },
 		];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/models/src/models/Messages.ts` around lines 46 - 82, The
scheduled-messages cron query (Messages.find({ scheduled: true, scheduledAt: {
$lte: now } })) is not supported by any existing index; update the Messages
modelIndexes() (in the Messages class) to add an index on scheduled and
scheduledAt — e.g. a compound index { scheduled: 1, scheduledAt: 1 } with a
partialFilterExpression { scheduled: true } (or sparse:true) so the cron query
can use the index and avoid full collection scans.
apps/meteor/server/startup/cron.ts (1)

22-22: Operational: ensure scheduled-message poll is supported by an index.

Per the relevant snippet, scheduledMessagesCron() runs every minute and executes Messages.find({ scheduled: true, scheduledAt: { $lte: now } }). MessagesRaw.modelIndexes() (see packages/models/src/models/Messages.ts) has no index covering scheduled/scheduledAt, so this becomes a recurring full-collection scan as the messages collection grows. Add a sparse partial index, e.g.:

{ key: { scheduledAt: 1 }, sparse: true, partialFilterExpression: { scheduled: true } },

Also worth confirming the 1-minute granularity is acceptable for the UX (users picking "send at 14:30:30" will see delivery up to ~60s late).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/server/startup/cron.ts` at line 22, The scheduled-messages cron
(scheduledMessagesCron) performs Messages.find({ scheduled: true, scheduledAt: {
$lte: now } }) every minute but there is no index for scheduled/scheduledAt in
the Messages model, causing full collection scans; update the model index list
in MessagesRaw.modelIndexes (in the Messages model) to add a partial/sparse
index on scheduledAt that only covers scheduled messages (e.g., an index with
key { scheduledAt: 1 } and partialFilterExpression { scheduled: true } or
sparse: true) so the query can use an index; also confirm that the current
1-minute cron granularity is acceptable for UX (possible up-to-60s delivery
delay).
apps/meteor/app/api/server/index.ts (1)

13-14: Consider consolidating with ./v1/chat.

The new endpoints are still part of the chat.* namespace; co-locating their registration in ./v1/chat (as siblings of the existing chat endpoints) would avoid a separate side-effect-only module and keep route discovery centralized. Not a blocker if you prefer to keep scheduling concerns isolated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/api/server/index.ts` around lines 13 - 14, The two separate
side-effect-only imports (./v1/chat.scheduleMessage and ./v1/chat) register
endpoints in the same chat.* namespace; move the route/endpoint registration
logic from ./v1/chat.scheduleMessage into the existing ./v1/chat module so all
chat.* routes are declared as siblings in one file, then remove the standalone
import of ./v1/chat.scheduleMessage from apps/meteor/app/api/server/index.ts;
update any export/initialization in ./v1/chat as needed to ensure
scheduleMessage registration runs on import.
apps/meteor/client/views/room/composer/messageBox/hooks/useScheduleMessageButton.tsx (1)

11-11: Add an explicit return type to the hook.

The hook returns { scheduleMenu: JSX.Element } but the return type is inferred. Add an explicit return type for clarity and to keep the public surface stable across edits.

♻️ Suggested signature
-export const useScheduleMessageButton = (roomId: string, tmid?: string) => {
+export const useScheduleMessageButton = (roomId: string, tmid?: string): { scheduleMenu: ReactElement } => {

(also import type { ReactElement } from 'react';)

As per coding guidelines: "Write concise, technical TypeScript/JavaScript with accurate typing".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/meteor/client/views/room/composer/messageBox/hooks/useScheduleMessageButton.tsx`
at line 11, The hook useScheduleMessageButton currently relies on an inferred
return type; update its signature to an explicit return type of { scheduleMenu:
ReactElement } (or JSX.Element) and add an import type { ReactElement } from
'react'; ensure the function declaration exports: export const
useScheduleMessageButton = (roomId: string, tmid?: string): { scheduleMenu:
ReactElement } => { ... } so the public surface remains stable.
apps/meteor/app/api/server/v1/chat.scheduleMessage.ts (1)

7-9: Operational hardening: bound and rate-limit scheduling.

Three concerns to consider before shipping:

  1. Cap the number of pending scheduled messages per user (and globally per room) to avoid abuse / cron starvation.
  2. Cap how far in the future scheduledAt may be (e.g., 1 year). Right now any future timestamp is accepted, which can keep "ghost" messages in storage indefinitely.
  3. Add this endpoint to the standard rate-limiter (most chat.* endpoints set rateLimiterOptions).

These can be applied via the existing rateLimiterOptions config plus a guarded count via Messages.countDocuments({ scheduled: true, 'u._id': this.userId }).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/api/server/v1/chat.scheduleMessage.ts` around lines 7 - 9,
Add operational guards to the API.v1 chat schedule endpoint: attach appropriate
rateLimiterOptions when registering the route (consistent with other chat.*
endpoints) and enforce limits before saving a scheduled message by (1) checking
per-user pending scheduled count via Messages.countDocuments({ scheduled: true,
'u._id': this.userId }) and rejecting when it exceeds the per-user cap, (2)
checking per-room pending scheduled count via Messages.countDocuments({
scheduled: true, rid: roomId }) and rejecting when it exceeds the per-room cap,
and (3) validating scheduledAt to reject timestamps beyond a maximum allowed
horizon (e.g., now + 1 year); return explicit errors for each violation. Ensure
these checks run in the same handler that registers under API.v1 so they prevent
creation and are covered by the rate limiter.
packages/rest-typings/src/v1/chat.ts (1)

1019-1046: Add AJV validators alongside the new endpoint typings.

Every other endpoint in this module (e.g., isChatPostMessageProps, isChatGetThreadsListProps) ships an ajv.compile/ajvQuery.compile-derived validator. The three new scheduled endpoints are declared inline only, with no exported is*Props guard, which breaks the established pattern and prevents reuse by callers that need runtime validation. Define ChatScheduleMessage, ChatGetScheduledMessages, ChatCancelScheduledMessage types + JSON schemas and export the corresponding ajv.compile validators (or move them to the chat/scheduled.ts module the AI summary mentions and re-export from here).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rest-typings/src/v1/chat.ts` around lines 1019 - 1046, The new
inline endpoints lack runtime AJV validators; add concrete types
ChatScheduleMessage, ChatGetScheduledMessages, ChatCancelScheduledMessage and
corresponding JSON schemas, then export AJV-compiled guards (e.g.,
isChatScheduleMessageProps, isChatGetScheduledMessagesProps,
isChatCancelScheduledMessageProps) using ajv.compile/ajvQuery.compile consistent
with existing validators; either define these types/schemas and exported
validators in this file or implement them in chat/scheduled.ts and re-export the
compiled validators here so callers can perform runtime validation the same way
as other endpoints like isChatPostMessageProps.
apps/meteor/client/views/room/composer/messageBox/MessageBoxScheduledMessagesModal.tsx (1)

23-41: Prefer useCallback and remove any from the response mapping.

Two small but concrete improvements:

  1. useMemo(() => async () => { ... }) returns the function as the memoized value — that is the exact role of useCallback. Switch to useCallback for idiomatic React and to match the rest of this file.
  2. (msg: any) => … and the as any casts hide that REST responses arrive as JSON (string dates), which is exactly the conversion happening on lines 30–31. Type the response shape explicitly so the Date | string union is enforced rather than silenced.
♻️ Suggested change
-	const loadScheduledMessages = useMemo(
-		() => async () => {
-			try {
-				setLoading(true);
-				const result = await getScheduledMessages({ roomId });
-				const messages = result.messages.map((msg: any) => ({
-					...msg,
-					ts: typeof msg.ts === 'string' ? new Date(msg.ts) : msg.ts,
-					scheduledAt: typeof msg.scheduledAt === 'string' ? new Date(msg.scheduledAt) : msg.scheduledAt,
-				}));
-				setScheduledMessages(messages);
-			} catch (error: any) {
-				dispatchToastMessage({ type: 'error', message: error.message });
-			} finally {
-				setLoading(false);
-			}
-		},
-		[roomId, getScheduledMessages, dispatchToastMessage],
-	);
+	const loadScheduledMessages = useCallback(async () => {
+		try {
+			setLoading(true);
+			const result = await getScheduledMessages({ roomId });
+			const messages = result.messages.map((msg) => ({
+				...msg,
+				ts: typeof msg.ts === 'string' ? new Date(msg.ts) : msg.ts,
+				scheduledAt:
+					typeof msg.scheduledAt === 'string' ? new Date(msg.scheduledAt) : msg.scheduledAt,
+			}));
+			setScheduledMessages(messages);
+		} catch (error) {
+			dispatchToastMessage({ type: 'error', message: error instanceof Error ? error.message : t('Error') });
+		} finally {
+			setLoading(false);
+		}
+	}, [roomId, getScheduledMessages, dispatchToastMessage, t]);

Apply the same error instanceof Error ? error.message : t('Error') pattern in handleCancel (line 53) so users don't see a literal undefined toast when the SDK throws a non-Error value.

As per coding guidelines: "Write concise, technical TypeScript/JavaScript with accurate typing".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/meteor/client/views/room/composer/messageBox/MessageBoxScheduledMessagesModal.tsx`
around lines 23 - 41, Replace the useMemo wrapper with useCallback for the async
loader (rename/keep loadScheduledMessages) and strictly type the REST response
instead of using any: define an interface for the scheduled message payload
(e.g. fields including ts: string | Date and scheduledAt: string | Date), call
getScheduledMessages(): Promise<{ messages: ScheduledMessagePayload[] }>, map
each msg to convert string dates to Date and pass to setScheduledMessages; also
update handleCancel to use error instanceof Error ? error.message : t('Error')
when dispatching the toast so non-Error throws don't produce "undefined".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/rare-llamas-behave.md:
- Around line 1-72: The changeset file .changeset/rare-llamas-behave.md
currently lists ~40 packages with no actual code changes; update it to include
only packages that had real modifications by removing entries for packages with
zero file changes (e.g., `@rocket.chat/favicon`, `@rocket.chat/peggy-loader`,
`@rocket.chat/jest-presets`, `@rocket.chat/eslint-config`,
`@rocket.chat/password-policies`, `@rocket.chat/cas-validate`, `@rocket.chat/tracing`,
`@rocket.chat/logger`, `@rocket.chat/jwt`, `@rocket.chat/cron`, etc.), leaving only
the true-changed packages such as `@rocket.chat/meteor`, `@rocket.chat/apps-engine`,
`@rocket.chat/rest-typings`, `@rocket.chat/core-typings`,
`@rocket.chat/model-typings`, `@rocket.chat/models`, `@rocket.chat/i18n` and the other
~20–25 packages you confirmed; ensure the final YAML list in
.changeset/rare-llamas-behave.md matches the actual diff (remove extraneous
package lines and save the file).

In `@apps/meteor/app/api/server/v1/chat.scheduleMessage.ts`:
- Around line 79-83: The query params (this.queryParams) are strings so the
check calls using Match.Maybe(Number) will fail; convert/normalize count and
offset to numbers before validating (e.g., parseInt/Number with fallback
defaults) or reuse the existing pagination helper getPaginationItems to parse
and validate them; update the destructuring in chat.scheduleMessage.ts to accept
raw strings, coerce count and offset to numeric values, then run check against
Number (or validate the parsed values) and proceed using the parsed numeric
count/offset.
- Around line 136-153: The cancel flow is non-atomic: replace the separate
Messages.findOneById + checks + Messages.deleteOne with a single conditional
atomic delete on Messages that includes _id, 'u._id' equal to this.userId, and
scheduled: true (e.g., deleteOne({ _id: messageId, 'u._id': this.userId,
scheduled: true })), then inspect the delete result/deletedCount and throw the
appropriate Meteor.Error ('error-message-not-found' / 'error-not-allowed' /
'error-not-scheduled') if nothing was deleted; keep the same error
types/messages and method names (chat.cancelScheduledMessage) so callers/crons
can rely on them, and also ensure the scheduledMessages cron uses a similar
atomic claim pattern as noted.
- Line 1: Remove the file-level "// `@ts-nocheck`" from chat.scheduleMessage.ts
and add proper TypeScript types and AJV-validated runtime checks: replace any
uses of loose types like messageData: any and untyped
this.bodyParams/this.queryParams with the corresponding is*Props validators from
packages/rest-typings (see v1/chat.ts) to validate and type the request shape,
then type the Messages.insertOne call with the correct
MessageInsert/MessageDocument type so the insert argument is fully typed and
validated before insertion; ensure you import the appropriate isXProps
validators and apply them to parse/validate request data prior to using
messageData or calling Messages.insertOne.
- Around line 32-43: Replace the read-only permission check with a write/send
permission check: call canSendMessageAsync(roomId, this.userId) instead of
canAccessRoomIdAsync and throw the same Meteor.Error('error-not-allowed', ...)
when it fails; remove the now-duplicate Users.findOneById lookup that the
comment mentions; additionally validate the message body is non-empty after
trimming and enforce the Message_MaxAllowedSize setting before scheduling so the
server does not accept oversized or empty messages; keep the existing
date-parsing/validation (scheduledDate, scheduledAt) and error types
(error-invalid-date, error-past-date) intact.

In `@apps/meteor/client/lib/chats/flows/scheduleMessage.ts`:
- Line 26: Replace the misleading success toast that uses t('Message_sent') when
a message is scheduled: update both dispatchToastMessage call sites in
scheduleMessage.ts (the dispatchToastMessage(...) in the scheduling flow) and
useScheduleMessageButton.tsx (the dispatchToastMessage(...) at line ~38) to use
a scheduling-specific i18n key such as t('Message_scheduled'), and add the
corresponding "Message_scheduled" entries to all locale files so translations
are available; ensure the toast type remains 'success' and the message string
uses the new key consistently in both locations.
- Around line 7-31: The exported function scheduleMessage is dead code; either
remove it or refactor callers to use it for consistency. Option A: delete the
scheduleMessage export and its file-level export if no other references exist
(remove scheduleMessage from
apps/meteor/client/lib/chats/flows/scheduleMessage.ts). Option B: update
useScheduleMessageButton to call the scheduleMessage flow instead of directly
using useEndpoint('POST', '/v1/chat.scheduleMessage')—import scheduleMessage
into useScheduleMessageButton, adapt its payload to match scheduleMessage({ rid,
msg, scheduledAt, tmid }) and handle success/error the same way as sendMessage
is used in ChatMessages.ts. Ensure only one consistent pattern remains across
sendMessage, scheduleMessage, and useScheduleMessageButton.

In
`@apps/meteor/client/views/room/composer/messageBox/MessageBoxScheduledMessagesModal.tsx`:
- Around line 99-108: The Cancel button is icon-only and relies on title which
isn't a reliable accessible name; in the MessageBoxScheduledMessagesModal
component update the Button (the one rendering <Icon name='trash' /> and calling
handleCancel(message._id)) to include an explicit aria-label using the
translated string (aria-label={t('Cancel')}) so screen readers announce the
action.

In
`@apps/meteor/client/views/room/composer/messageBox/MessageBoxScheduleModal.tsx`:
- Around line 15-48: The code mixes UTC strings from toISOString() with local
time inputs causing wrong defaults and min validation; fix by using local-based
formatting and local Date construction: replace defaultDate/defaultTime and
currentDate/currentTime (now and tomorrow) generation with helper local
formatters (e.g., formatLocalDate(now) -> "YYYY-MM-DD" and formatLocalTime(now)
-> "HH:MM") so defaultDate/defaultTime, currentDate/currentTime and minDate use
local calendar values, compute tomorrow by adding 1 day on the local Date,
compute minTime as current local time when date === currentDate, and in
handleSchedule replace new Date(`${date}T${time}`) with a local constructor (new
Date(year, month-1, day, hour, minute)) parsed from the date/time strings so
scheduledAt is built in local timezone; update references to defaultDate,
defaultTime, currentDate, currentTime, minDate, minTime and the scheduledAt
creation in handleSchedule accordingly.

In `@apps/meteor/server/cron/scheduledMessages.ts`:
- Around line 46-50: The catch block around sendMessage currently deletes the
scheduledMessage on any error and swallows the exception; change the catch in
the send flow (where scheduledMessage, messageDeleted, sendMessage and
Messages.deleteOne are referenced) to (1) log the error with structured context
(include scheduledMessage._id, scheduledMessage.rid, scheduledMessage.userId or
authorId) using the project's logger, (2) do not delete the record on transient
failures — instead update the scheduledMessage with a failure marker (e.g., set
scheduledFailedAt to new Date() and optionally increment a retryCount) via
Messages.updateOne({ _id: scheduledMessage._id }, { $set: { scheduledFailedAt:
... } }), and (3) rethrow or surface the error so it isn't silently swallowed
(or at minimum emit/record a metric/alert) so operational teams can investigate.
Ensure messageDeleted still controls permanent deletion only when appropriate.
- Around line 10-13: The current Messages.find({ scheduled: true, scheduledAt: {
$lte: now } }).toArray() is racy and unbounded; replace the read-then-delete
pattern with an atomic per-message claim and bounded batch: issue a limited
query (e.g., limit N) and for each candidate use an atomic findOneAndUpdate on
Messages with the same filter plus a not-yet-claimed predicate (e.g., { _id: id,
scheduled: true, scheduledAt: { $lte: now }, claimed: { $ne: true } }) setting
claimed=true and claimedAt=now (or a worker id) — only proceed to call
sendMessage for documents returned by that findOneAndUpdate; remove the
standalone deleteOne and the messageDeleted flag usage, and iterate in bounded
batches until no more documents are claimable to avoid OOM and duplicate sends.

In `@packages/rest-typings/src/v1/chat.ts`:
- Around line 1019-1033: The TypeScript typing for the
'/v1/chat.scheduleMessage' POST params advertises fields the server's check() in
chat.scheduleMessage does not accept; either remove
alias/avatar/emoji/attachments/customFields from the POST params or extend the
server-side check() to validate and accept those fields so they aren't rejected
at runtime—also replace attachments?: any[] with attachments?:
MessageAttachment[] and customFields?: IMessage['customFields'] to use accurate
types; update the POST params definition (the '/v1/chat.scheduleMessage' POST
params object) and/or the server-side check() in chat.scheduleMessage
accordingly so types and runtime validation match.

In `@packages/rest-typings/src/v1/chat/scheduled.ts`:
- Around line 26-31: The schema is using "nullable: true" for properties (tmid,
alias, avatar, emoji, attachments, customFields, and the ones noted around lines
49-50) which allows null at runtime but TypeScript types are optional (T |
undefined); either remove "nullable: true" from these property schemas to match
the TS types, or update the TS declarations to allow null (e.g. field?: T |
null) for each listed property; locate the property entries in the scheduled
message schema (the object containing tmid, alias, avatar, emoji, attachments,
customFields) and apply the chosen change consistently.
- Around line 16-17: Replace the loose any types for attachments and
customFields with the project's established types: change attachments?: any[] to
attachments?: MessageAttachment[] and change customFields?: Record<string, any>
to customFields?: IMessage['customFields'] (or the exact customFields type
exported by IMessage), and add the necessary imports for MessageAttachment and
IMessage from the core-typings module so the new types are consistent with the
main chat.ts and IMessage definitions.

---

Outside diff comments:
In `@apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx`:
- Around line 502-515: The scheduleMenu is rendered when canSend is true but is
not disabled during isRecording, isUploading, or isProcessingUploads; update
either the call to useScheduleMessageButton (pass a disabled prop derived from
isRecording || isUploading || isProcessingUploads) so the hook sets
GenericMenu's disabled state, or wrap scheduleMenu with an additional
conditional render so it only renders when canSend && !isRecording &&
!isUploading && !isProcessingUploads; locate usages of scheduleMenu and the hook
useScheduleMessageButton in MessageBox.tsx and ensure the GenericMenu produced
by the hook respects the disabled flag.

In `@packages/models/src/models/Messages.ts`:
- Around line 84-106: The visible-query methods are leaking scheduled (unsent)
messages; update findVisibleByMentionAndRoomId and
findPaginatedVisibleByMentionAndRoomId to include scheduled: { $ne: true } in
their query, and factor the common visibility predicate into a helper (e.g.,
getVisibleMessageFilter or VISIBLE_MESSAGE_FILTER) that returns { _hidden: {
$ne: true }, scheduled: { $ne: true } } so you can reuse it; then apply that
helper consistently to all "visible*" and "countVisible*" methods called out in
the review (findVisibleByIds, findVisibleThreadByThreadId,
getLastVisibleUserMessageSentByRoomId,
countVisibleByRoomIdBetweenTimestampsNotContainingTypes,
countVisibleByRoomIdBetweenTimestampsInclusive, findForUpdates,
countPinned/findPaginatedPinnedByRoom, countStarred/findStarredByUserAtRoom) to
eliminate count/find drift and mention/thread leaks.

---

Nitpick comments:
In `@apps/meteor/app/api/server/index.ts`:
- Around line 13-14: The two separate side-effect-only imports
(./v1/chat.scheduleMessage and ./v1/chat) register endpoints in the same chat.*
namespace; move the route/endpoint registration logic from
./v1/chat.scheduleMessage into the existing ./v1/chat module so all chat.*
routes are declared as siblings in one file, then remove the standalone import
of ./v1/chat.scheduleMessage from apps/meteor/app/api/server/index.ts; update
any export/initialization in ./v1/chat as needed to ensure scheduleMessage
registration runs on import.

In `@apps/meteor/app/api/server/v1/chat.scheduleMessage.ts`:
- Around line 7-9: Add operational guards to the API.v1 chat schedule endpoint:
attach appropriate rateLimiterOptions when registering the route (consistent
with other chat.* endpoints) and enforce limits before saving a scheduled
message by (1) checking per-user pending scheduled count via
Messages.countDocuments({ scheduled: true, 'u._id': this.userId }) and rejecting
when it exceeds the per-user cap, (2) checking per-room pending scheduled count
via Messages.countDocuments({ scheduled: true, rid: roomId }) and rejecting when
it exceeds the per-room cap, and (3) validating scheduledAt to reject timestamps
beyond a maximum allowed horizon (e.g., now + 1 year); return explicit errors
for each violation. Ensure these checks run in the same handler that registers
under API.v1 so they prevent creation and are covered by the rate limiter.

In
`@apps/meteor/client/views/room/composer/messageBox/hooks/useScheduleMessageButton.tsx`:
- Line 11: The hook useScheduleMessageButton currently relies on an inferred
return type; update its signature to an explicit return type of { scheduleMenu:
ReactElement } (or JSX.Element) and add an import type { ReactElement } from
'react'; ensure the function declaration exports: export const
useScheduleMessageButton = (roomId: string, tmid?: string): { scheduleMenu:
ReactElement } => { ... } so the public surface remains stable.

In
`@apps/meteor/client/views/room/composer/messageBox/MessageBoxScheduledMessagesModal.tsx`:
- Around line 23-41: Replace the useMemo wrapper with useCallback for the async
loader (rename/keep loadScheduledMessages) and strictly type the REST response
instead of using any: define an interface for the scheduled message payload
(e.g. fields including ts: string | Date and scheduledAt: string | Date), call
getScheduledMessages(): Promise<{ messages: ScheduledMessagePayload[] }>, map
each msg to convert string dates to Date and pass to setScheduledMessages; also
update handleCancel to use error instanceof Error ? error.message : t('Error')
when dispatching the toast so non-Error throws don't produce "undefined".

In `@apps/meteor/server/startup/cron.ts`:
- Line 22: The scheduled-messages cron (scheduledMessagesCron) performs
Messages.find({ scheduled: true, scheduledAt: { $lte: now } }) every minute but
there is no index for scheduled/scheduledAt in the Messages model, causing full
collection scans; update the model index list in MessagesRaw.modelIndexes (in
the Messages model) to add a partial/sparse index on scheduledAt that only
covers scheduled messages (e.g., an index with key { scheduledAt: 1 } and
partialFilterExpression { scheduled: true } or sparse: true) so the query can
use an index; also confirm that the current 1-minute cron granularity is
acceptable for UX (possible up-to-60s delivery delay).

In `@packages/models/src/models/Messages.ts`:
- Around line 46-82: The scheduled-messages cron query (Messages.find({
scheduled: true, scheduledAt: { $lte: now } })) is not supported by any existing
index; update the Messages modelIndexes() (in the Messages class) to add an
index on scheduled and scheduledAt — e.g. a compound index { scheduled: 1,
scheduledAt: 1 } with a partialFilterExpression { scheduled: true } (or
sparse:true) so the cron query can use the index and avoid full collection
scans.

In `@packages/rest-typings/src/v1/chat.ts`:
- Around line 1019-1046: The new inline endpoints lack runtime AJV validators;
add concrete types ChatScheduleMessage, ChatGetScheduledMessages,
ChatCancelScheduledMessage and corresponding JSON schemas, then export
AJV-compiled guards (e.g., isChatScheduleMessageProps,
isChatGetScheduledMessagesProps, isChatCancelScheduledMessageProps) using
ajv.compile/ajvQuery.compile consistent with existing validators; either define
these types/schemas and exported validators in this file or implement them in
chat/scheduled.ts and re-export the compiled validators here so callers can
perform runtime validation the same way as other endpoints like
isChatPostMessageProps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d0f58417-c1d7-4b0c-8ae0-84f9e93d6419

📥 Commits

Reviewing files that changed from the base of the PR and between ceefff9 and a792924.

📒 Files selected for processing (18)
  • .changeset/rare-llamas-behave.md
  • apps/meteor/app/api/server/index.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
  • apps/meteor/client/lib/chats/flows/scheduleMessage.ts
  • apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx
  • apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/MessageBoxActionsToolbar.tsx
  • apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useViewScheduledMessagesAction.tsx
  • apps/meteor/client/views/room/composer/messageBox/MessageBoxScheduleMenu.tsx
  • apps/meteor/client/views/room/composer/messageBox/MessageBoxScheduleModal.tsx
  • apps/meteor/client/views/room/composer/messageBox/MessageBoxScheduledMessagesModal.tsx
  • apps/meteor/client/views/room/composer/messageBox/hooks/useScheduleMessageButton.tsx
  • apps/meteor/server/cron/scheduledMessages.ts
  • apps/meteor/server/startup/cron.ts
  • packages/core-typings/src/IMessage/IMessage.ts
  • packages/i18n/src/locales/en.i18n.json
  • packages/models/src/models/Messages.ts
  • packages/rest-typings/src/v1/chat.ts
  • packages/rest-typings/src/v1/chat/scheduled.ts

Comment thread .changeset/rare-llamas-behave.md
Comment thread apps/meteor/app/api/server/v1/chat.scheduleMessage.ts Outdated
Comment thread apps/meteor/app/api/server/v1/chat.scheduleMessage.ts Outdated
Comment thread apps/meteor/app/api/server/v1/chat.scheduleMessage.ts Outdated
Comment thread apps/meteor/app/api/server/v1/chat.scheduleMessage.ts Outdated
Comment thread apps/meteor/server/cron/scheduledMessages.ts Outdated
Comment thread apps/meteor/server/cron/scheduledMessages.ts Outdated
Comment thread packages/rest-typings/src/v1/chat.ts
Comment thread packages/rest-typings/src/v1/chat/scheduled.ts Outdated
Comment thread packages/rest-typings/src/v1/chat/scheduled.ts Outdated
@coderabbitai coderabbitai Bot removed the type: feature Pull requests that introduces new feature label Apr 25, 2026
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: 4

♻️ Duplicate comments (2)
apps/meteor/app/api/server/v1/chat.scheduleMessage.ts (2)

30-73: ⚠️ Potential issue | 🟡 Minor

Server-side message validation (non-empty + Message_MaxAllowedSize) is missing.

The endpoint inserts straight into Messages via Messages.insertOne(messageData as IMessage), bypassing the validateMessage/length checks that the normal sendMessage flow runs. A client can therefore schedule an empty (or whitespace-only) msg, or a message exceeding Message_MaxAllowedSize, which will then be delivered by the cron a minute later and cause errors at delivery (or pollute the room).

Validate before insertion:

  • message.trim().length > 0
  • message.length <= settings.get<number>('Message_MaxAllowedSize')

Throw a clear Meteor.Error('error-invalid-message', ...) on failure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/api/server/v1/chat.scheduleMessage.ts` around lines 30 - 73,
The scheduled message handler currently inserts user content directly via
Messages.insertOne (see messageData / Messages.insertOne) without validating
content; before building/inserting messageData in chat.scheduleMessage, validate
that message.trim().length > 0 and that message.length <=
settings.get<number>('Message_MaxAllowedSize'), and if either check fails throw
new Meteor.Error('error-invalid-message', 'Invalid message', { method:
'chat.scheduleMessage' }); perform these checks immediately after extracting
message/scheduledAt and before calling canSendMessageAsync or any DB insert so
invalid messages are rejected server-side.

13-35: 🛠️ Refactor suggestion | 🟠 Major

Replace this: any and meteor/check with route-level AJV body/query schemas.

All three handlers (lines 29, 106, 165) declare this: any and rely on meteor/check for input validation, but the route options only define response schemas — no body/query schemas. This:

  • Violates the coding guideline on accurate typing (the parameters of every endpoint are effectively any).
  • Diverges from the established pattern of other migrated endpoints in apps/meteor/app/api/server/v1/, which validate body and query via AJV (is*Props-style validators).
  • Causes the redundant runtime guard at line 110 (check(roomId, String) after the value was already destructured and partially used by getPaginationItems).

A corresponding is*Props set should live in packages/rest-typings/src/v1/chat/scheduled.ts (the file already appears in this PR's import surface) so the route can be wired with body: isChatScheduleMessageProps etc., and the meteor/check calls can be dropped.

As per coding guidelines: "Write concise, technical TypeScript/JavaScript with accurate typing".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/api/server/v1/chat.scheduleMessage.ts` around lines 13 - 35,
The route's action uses an untyped this: any and runtime validation via
meteor/check (check(this.bodyParams, ...)) instead of a route-level AJV schema;
replace the runtime checks by adding an AJV body schema (e.g., body:
isChatScheduleMessageProps) to the route options, remove the meteor/check calls
inside the action, and update the action signature to a properly typed handler
(remove this: any) so request params are typed; add the
isChatScheduleMessageProps validator in
packages/rest-typings/src/v1/chat/scheduled.ts (or use the existing one if
present) and wire it into the route options for chat.scheduleMessage (and the
other two handlers at lines noted) to match the is*Props pattern used across v1
endpoints.
🧹 Nitpick comments (3)
apps/meteor/server/cron/scheduledMessages.ts (2)

48-58: Type the dispatch payload instead of any.

const message: any = { ... } defeats type-checking on the call to sendMessage. Use the matching Partial<IMessage> (or the parameter type accepted by sendMessage) so accidental field renames in IMessage (e.g., tmid, attachments) get caught at compile time. Same for the inner field-presence checks — they can be expressed via a typed builder.

As per coding guidelines: "Write concise, technical TypeScript/JavaScript with accurate typing".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/server/cron/scheduledMessages.ts` around lines 48 - 58, The
current message object is typed as any which bypasses compile-time checks;
change the declaration to the correct type (e.g., Partial<IMessage> or the exact
parameter type expected by sendMessage) and build the object using that type so
missing/renamed fields (tmid, attachments, alias, avatar, emoji, etc.) are
caught by the compiler; update the construction around the message variable and
the call to sendMessage(user, message, room, {}) to use this typed payload and
prefer assigning optional properties via typed conditionals or a typed builder
to avoid using && spreads with an any type.

4-6: Simplify the SystemLogger import path for consistency.

The current import '../../server/lib/logger/system' unnecessarily traverses through the root directory. Other files in apps/meteor/server/cron/ import their sibling server modules using the shorter form: userDataDownloads.ts uses '../lib/dataExport' and oembed.ts uses '../methods/OEmbedCacheCleanup'. Align this import to '../lib/logger/system' for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/server/cron/scheduledMessages.ts` around lines 4 - 6, Update the
SystemLogger import in scheduledMessages.ts to use the shorter sibling-relative
path; replace the current import from '../../server/lib/logger/system' with
'../lib/logger/system' so the file imports SystemLogger consistently with other
cron modules (refer to the SystemLogger symbol in scheduledMessages.ts).
apps/meteor/app/api/server/v1/chat.scheduleMessage.ts (1)

118-130: Drop the redundant Number(offset) / Number(count) casts.

getPaginationItems already parses and returns { offset: number; count: number }. Wrapping them in Number(...) again is dead conversion noise and slightly obscures the types. Pass them directly:

♻️ Suggested change
-			{
-				sort: { scheduledAt: 1 },
-				skip: Number(offset),
-				limit: Number(count),
-			},
+			{
+				sort: { scheduledAt: 1 },
+				skip: offset,
+				limit: count,
+			},

Also consider replacing the two-query pattern (find().toArray() + countDocuments) with Messages.findPaginated(...).toArray() and the total from the same call, mirroring other listing endpoints.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/api/server/v1/chat.scheduleMessage.ts` around lines 118 -
130, The code redundantly wraps offset and count in Number(...) when
getPaginationItems already returns numbers; update the Messages.find call to
pass offset and count directly (use offset and count variables instead of
Number(offset)/Number(count)) and optionally replace the two-query pattern
(find(...).toArray() + countDocuments) with Messages.findPaginated(...) so you
can call Messages.findPaginated({ rid: roomId, 'u._id': this.userId, scheduled:
true, scheduledAt: { $exists: true } }, { sort: { scheduledAt: 1 }, skip:
offset, limit: count }).toArray() and obtain the total from the same paginated
call to mirror other listing endpoints.
🤖 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/app/api/server/v1/chat.scheduleMessage.ts`:
- Around line 59-73: When building messageData and before calling
Messages.insertOne, validate any provided tmid by fetching the parent message
(e.g., via Messages.findOne({_id: tmid})) and ensure the parent exists and
parent.rid === roomId; if not, throw/reject the request. This keeps the
same-room thread invariant enforced here (matching what sendMessage enforces at
delivery), so update the scheduling handler that constructs messageData (and the
call site around Messages.insertOne) to perform that lookup and validation and
abort insertion on mismatch.
- Around line 53-57: The try/catch around canSendMessageAsync in
chat.scheduleMessage (and the similar block in chat.getScheduledMessages) is
collapsing distinct errors into a generic Meteor.Error('error-not-allowed');
remove the catch so canSendMessageAsync's original errors propagate, or if you
must catch, rethrow the original error (throw error) instead of replacing it
with 'error-not-allowed', ensuring codes like 'error-invalid-room',
'room_is_archived', 'room_is_blocked', 'You_have_been_muted' and read-only
errors are preserved; update the blocks referencing canSendMessageAsync and any
Meteor.Error rethrows in scheduleMessage and getScheduledMessages accordingly.

In `@apps/meteor/server/cron/scheduledMessages.ts`:
- Around line 58-73: The catch-all reset is causing duplicate sends and infinite
retries; instead, change the workflow in scheduledMessages.ts so that you either
remove the DB row before calling sendMessage (rely on the existing atomic claim
around lines 20-32 and pass the document by value), or if you keep
deleteAfterSend, do NOT flip scheduled back to true when Messages.deleteOne
fails after a successful send—log the delete failure and leave scheduled:false
so it won't be re-delivered; additionally add a retry cap by introducing fields
like scheduledRetryCount and scheduledFailedAt on the message document,
increment scheduledRetryCount on send failures (use the existing catch path that
currently calls Messages.updateOne) and stop retrying / surface the failure once
scheduledRetryCount >= N (or set scheduledFailedAt) so malformed or persistent
errors become dead-lettered instead of being retried forever.
- Around line 40-46: The catch block around canSendMessageAsync currently
deletes the scheduled message silently; instead, log the failure with
messageId/roomId/userId using the project logger (e.g., processLogger.error or
Logger) and persist failure metadata on the scheduled message rather than
deleting it: update Messages (Messages.updateOne({ _id: scheduledMessage._id },
{ $set: { scheduledFailedAt: new Date(), failureReason: error?.message ||
String(error) } })) so the UI can surface the problem; only perform
Messages.deleteOne for explicit non-recoverable errors (e.g., user not found)
after checking error type/message. Ensure you reference canSendMessageAsync,
scheduledMessage, Messages.deleteOne/updateOne, scheduledFailedAt, and
failureReason when making the change.

---

Duplicate comments:
In `@apps/meteor/app/api/server/v1/chat.scheduleMessage.ts`:
- Around line 30-73: The scheduled message handler currently inserts user
content directly via Messages.insertOne (see messageData / Messages.insertOne)
without validating content; before building/inserting messageData in
chat.scheduleMessage, validate that message.trim().length > 0 and that
message.length <= settings.get<number>('Message_MaxAllowedSize'), and if either
check fails throw new Meteor.Error('error-invalid-message', 'Invalid message', {
method: 'chat.scheduleMessage' }); perform these checks immediately after
extracting message/scheduledAt and before calling canSendMessageAsync or any DB
insert so invalid messages are rejected server-side.
- Around line 13-35: The route's action uses an untyped this: any and runtime
validation via meteor/check (check(this.bodyParams, ...)) instead of a
route-level AJV schema; replace the runtime checks by adding an AJV body schema
(e.g., body: isChatScheduleMessageProps) to the route options, remove the
meteor/check calls inside the action, and update the action signature to a
properly typed handler (remove this: any) so request params are typed; add the
isChatScheduleMessageProps validator in
packages/rest-typings/src/v1/chat/scheduled.ts (or use the existing one if
present) and wire it into the route options for chat.scheduleMessage (and the
other two handlers at lines noted) to match the is*Props pattern used across v1
endpoints.

---

Nitpick comments:
In `@apps/meteor/app/api/server/v1/chat.scheduleMessage.ts`:
- Around line 118-130: The code redundantly wraps offset and count in
Number(...) when getPaginationItems already returns numbers; update the
Messages.find call to pass offset and count directly (use offset and count
variables instead of Number(offset)/Number(count)) and optionally replace the
two-query pattern (find(...).toArray() + countDocuments) with
Messages.findPaginated(...) so you can call Messages.findPaginated({ rid:
roomId, 'u._id': this.userId, scheduled: true, scheduledAt: { $exists: true } },
{ sort: { scheduledAt: 1 }, skip: offset, limit: count }).toArray() and obtain
the total from the same paginated call to mirror other listing endpoints.

In `@apps/meteor/server/cron/scheduledMessages.ts`:
- Around line 48-58: The current message object is typed as any which bypasses
compile-time checks; change the declaration to the correct type (e.g.,
Partial<IMessage> or the exact parameter type expected by sendMessage) and build
the object using that type so missing/renamed fields (tmid, attachments, alias,
avatar, emoji, etc.) are caught by the compiler; update the construction around
the message variable and the call to sendMessage(user, message, room, {}) to use
this typed payload and prefer assigning optional properties via typed
conditionals or a typed builder to avoid using && spreads with an any type.
- Around line 4-6: Update the SystemLogger import in scheduledMessages.ts to use
the shorter sibling-relative path; replace the current import from
'../../server/lib/logger/system' with '../lib/logger/system' so the file imports
SystemLogger consistently with other cron modules (refer to the SystemLogger
symbol in scheduledMessages.ts).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e19e5d9a-e649-4dd6-b4e1-3a34beeac9ae

📥 Commits

Reviewing files that changed from the base of the PR and between a792924 and 02e39ac.

📒 Files selected for processing (5)
  • .changeset/rare-llamas-behave.md
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
  • apps/meteor/server/cron/scheduledMessages.ts
  • packages/rest-typings/src/v1/chat.ts
  • packages/rest-typings/src/v1/chat/scheduled.ts
✅ Files skipped from review due to trivial changes (2)
  • .changeset/rare-llamas-behave.md
  • packages/rest-typings/src/v1/chat/scheduled.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/rest-typings/src/v1/chat.ts
📜 Review details
🧰 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/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
🧠 Learnings (33)
📚 Learning: 2026-04-18T12:32:53.425Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat PR: 38623
File: apps/meteor/app/lib/server/functions/cleanRoomHistory.ts:146-149
Timestamp: 2026-04-18T12:32:53.425Z
Learning: In `apps/meteor/app/lib/server/functions/cleanRoomHistory.ts` (PR `#38623`), the read-receipt cleanup (both `ReadReceipts.removeByMessageIds` and `ReadReceiptsArchive.removeByMessageIds`) is intentionally only performed in the limited prune path (`limit && selectedMessageIds`). The unlimited/delete-all path (`limit === 0`) deliberately skips cleaning up orphaned read receipts in both hot and cold storage — this is by design. Do not flag this as a bug or missing cleanup in future reviews.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-23T19:33:46.159Z
Learnt from: cardoso
Repo: RocketChat/Rocket.Chat PR: 39818
File: apps/meteor/client/hooks/usePruneWarningMessage.ts:45-56
Timestamp: 2026-03-23T19:33:46.159Z
Learning: In RocketChat/Rocket.Chat, cron expressions used by the retention policy (e.g., `RetentionPolicy_Advanced_Precision_Cron`) follow the convention of the `cron` npm package (outdated version), which uses **0-indexed months** (0 = January, 11 = December), not the standard 1-12 used in POSIX cron. The local `sendAt` function in `apps/meteor/client/hooks/usePruneWarningMessage.ts` intentionally replicates this 0-indexed behavior. Do NOT suggest adding `+1` to `date.getMonth()` in this file, as it would break tests and create inconsistency with the backend.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-11T22:04:20.529Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 39545
File: apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts:59-61
Timestamp: 2026-03-11T22:04:20.529Z
Learning: In `apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts`, the `msg.u._id === uid` early-return in the `streamNewMessage` handler is intentional: the "New messages" indicator is designed to notify about messages from other users only. Self-sent messages — including those sent from a different session/device — are always skipped, by design. Do not flag this as a multi-session regression.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.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/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-02-24T19:16:35.307Z
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:35.307Z
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.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-04-17T23:32:07.223Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/messages.ts:348-352
Timestamp: 2026-04-17T23:32:07.223Z
Learning: In `apps/meteor/app/apps/server/converters/messages.ts`, the `timestamp` handler inside `_convertAttachmentsToApp` uses a non-null assertion (`attachment.ts!`) intentionally. The `ts` property on `MessageAttachment` is optional only to accommodate MessageAttachment creation-time scenarios; by the time `_convertAttachmentsToApp` is called, the message has already been stored and the attachment is guaranteed to have a `ts` value. Do not flag this non-null assertion as unsafe during code review.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-11T18:17:53.972Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39425
File: apps/meteor/client/lib/chats/flows/processMessageUploads.ts:112-119
Timestamp: 2026-03-11T18:17:53.972Z
Learning: In `apps/meteor/client/lib/chats/flows/processMessageUploads.ts`, when sending multiple file uploads, each file is confirmed via its own `/rooms.mediaConfirm/${rid}/${fileId}` call and produces a separate message. Only the first file's confirm payload carries the composed message text (`msg`); all subsequent files receive `msg: ''`. This one-message-per-file behavior is intentional by design — do not flag it as a bug or suggest batching into a single message.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-09T18:39:21.178Z
Learnt from: Harxhit
Repo: RocketChat/Rocket.Chat PR: 39476
File: apps/meteor/server/methods/addAllUserToRoom.ts:0-0
Timestamp: 2026-03-09T18:39:21.178Z
Learning: In apps/meteor/server/methods/addAllUserToRoom.ts, the implementation uses a single cursor pass (Users.find(userFilter).batchSize(100)) that collects both the full user objects (collectedUsers: IUser[]) and their usernames (usernames: string[]) in one iteration. `beforeAddUserToRoom` is then called once with the full usernames batch (preserving batch-validation semantics), and the subsequent subscription/message processing loop iterates over the same stable `collectedUsers` array — no second DB query is made. This avoids any race condition between validation and processing while preserving the original batch-validation behavior.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-16T11:50:18.066Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39657
File: apps/meteor/tests/end-to-end/apps/app-resolve-visitor.ts:18-21
Timestamp: 2026-03-16T11:50:18.066Z
Learning: In `apps/meteor/tests/end-to-end/apps/` (and Livechat E2E tests broadly), calling `createAgent()` and `makeAgentAvailable()` immediately after `updateSetting('Livechat_enabled', true)` — without any intermediate sleep — is an established, non-flaky pattern used across 10+ tests in the codebase. Do not flag this sequence as a potential race condition or suggest adding a delay between them.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Ensure tests run reliably in parallel without shared state conflicts

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.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/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-23T18:10:55.887Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 39857
File: apps/meteor/app/api/server/middlewares/metrics.ts:25-57
Timestamp: 2026-04-23T18:10:55.887Z
Learning: In RocketChat/Rocket.Chat, the route handler action wrapper in `apps/meteor/app/api/server/ApiClass.ts` (around line 892) contains a `catch` block that intercepts all route-handler errors and converts them into proper HTTP API response objects (e.g., `api.failure`, `api.unauthorized`, `api.tooManyRequests`). As a result, `next()` in Hono middleware (such as `metricsMiddleware` in `apps/meteor/app/api/server/middlewares/metrics.ts`) will never throw. Do not flag missing try/finally guards around `await next()` calls in this middleware for gauge/counter decrement safety, as the execution flow is never disrupted by route-level errors.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.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/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-04-23T18:14:24.851Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 39857
File: apps/meteor/app/lib/server/lib/debug.js:87-91
Timestamp: 2026-04-23T18:14:24.851Z
Learning: In `apps/meteor/app/lib/server/lib/debug.js`, the `wrapMethods` function wraps Meteor DDP method handlers inside a `tracerActiveSpan` callback. The `end()` (summary timer) and `endHistogram()` (histogram timer) calls are placed after `await originalHandler.apply(this, originalArgs)` without a try/finally guard. This is intentional and safe: errors thrown by Meteor method handlers are caught by the route-handler action wrapper in `apps/meteor/app/api/server/ApiClass.ts` (around line 892-893), which converts them into proper response objects, so execution flow is never disrupted and the timer calls are always reached. Do not flag the missing try/finally as a bug for these timer calls in `debug.js`.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-12T15:39:28.416Z
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.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-12T10:26:26.697Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 39340
File: apps/meteor/app/api/server/v1/im.ts:1349-1398
Timestamp: 2026-03-12T10:26:26.697Z
Learning: In `apps/meteor/app/api/server/v1/im.ts` (PR `#39340`), the `DmEndpoints` type intentionally includes temporary stub entries for `/v1/im.kick`, `/v1/dm.kick`, `/v1/im.leave`, and `/v1/dm.leave` (using `DmKickProps` and `DmLeaveProps`) even though no route handlers exist for them yet. These stubs were added to preserve type compatibility after removing the original `DmLeaveProps` and related files. They are planned for cleanup in a follow-up PR. Do not flag these as missing implementations when reviewing this file until the follow-up is merged.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-03T11:11:48.541Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 39230
File: apps/meteor/app/api/server/v1/chat.ts:214-222
Timestamp: 2026-03-03T11:11:48.541Z
Learning: In apps/meteor/server/lib/moderation/reportMessage.ts, the reportMessage function validates that description is not empty or whitespace-only with `if (!description.trim())`. When migrating the chat.reportMessage endpoint to OpenAPI, adding minLength validation to the schema preserves this existing behavior.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-14T14:58:58.834Z
Learnt from: smirk-dev
Repo: RocketChat/Rocket.Chat PR: 39625
File: apps/meteor/app/api/server/v1/push.ts:85-97
Timestamp: 2026-03-14T14:58:58.834Z
Learning: In RocketChat/Rocket.Chat, the `push.token` POST/DELETE endpoints in `apps/meteor/app/api/server/v1/push.ts` were already migrated to the chained router API pattern on `develop` prior to PR `#39625`. `cleanTokenResult` (which strips `authToken` and returns `PushTokenResult`) and `isPushTokenPOSTProps`/`isPushTokenDELETEProps` validators already exist on `develop`. PR `#39625` only migrates `push.get` and `push.info` to the chained pattern. Do not flag `cleanTokenResult` or `PushTokenResult` as newly introduced behavior-breaking changes when reviewing this PR.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-02-24T19:09:01.522Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38974
File: apps/meteor/app/api/server/v1/im.ts:220-221
Timestamp: 2026-02-24T19:09:01.522Z
Learning: In Rocket.Chat OpenAPI migration PRs for endpoints under apps/meteor/app/api/server/v1, avoid introducing logic changes. Only perform scope-tight changes that preserve behavior; style-only cleanups (e.g., removing inline comments) may be deferred to follow-ups to keep the migration PR focused.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-20T13:52:29.575Z
Learnt from: ggazzo
Repo: RocketChat/Rocket.Chat PR: 39553
File: apps/meteor/app/api/server/v1/stats.ts:98-117
Timestamp: 2026-03-20T13:52:29.575Z
Learning: In `apps/meteor/app/api/server/v1/stats.ts`, the `statistics.telemetry` POST endpoint intentionally has no `body` AJV schema in its route options. The proper request body shape (a `params` array of telemetry event objects) has not been formally defined yet, so body validation is deferred to a follow-up. Do not flag the missing body schema for this endpoint during OpenAPI migration reviews.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-15T14:31:28.969Z
Learnt from: amitb0ra
Repo: RocketChat/Rocket.Chat PR: 39647
File: apps/meteor/app/api/server/v1/users.ts:710-757
Timestamp: 2026-03-15T14:31:28.969Z
Learning: In RocketChat/Rocket.Chat, the `UserCreateParamsPOST` type in `apps/meteor/app/api/server/v1/users.ts` (migrated from `packages/rest-typings/src/v1/users/UserCreateParamsPOST.ts`) intentionally has `fields: string` (non-optional) and `settings?: IUserSettings` without a corresponding AJV schema entry. This is a pre-existing divergence carried over verbatim from the original rest-typings source (PR `#39647`). Do not flag this type/schema misalignment during the OpenAPI migration review — it is tracked as a separate follow-up fix.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-18T22:07:19.687Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/departments.ts:59-70
Timestamp: 2026-03-18T22:07:19.687Z
Learning: In `apps/meteor/app/apps/server/converters/departments.ts`, the `convertAppDepartment` method uses non-null assertions (`!`) on `department.name`, `department.email`, and `department.offlineMessageChannelName` when constructing `newDepartment: ILivechatDepartment`. These fields are optional on `IAppsDepartment`, but the App framework flow guarantees their presence at the call site. Do not flag these non-null assertions as unsafe during code review.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-04-20T17:11:59.452Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 40225
File: apps/meteor/ee/server/apps/communication/endpoints/appLogsHandler.ts:55-71
Timestamp: 2026-04-20T17:11:59.452Z
Learning: In `apps/meteor/ee/server/apps/communication/endpoints/appLogsHandler.ts`, the concern about an empty `?appId=` query param bypassing the truthy check and overriding the path `appId` in the `makeAppLogsQuery` spread is not relevant. The AJV query schema (`isAppLogsProps`) validates and rejects invalid/empty `appId` values before the action handler is reached, making the in-handler guard sufficient as-is. Do not flag this pattern as a vulnerability in future reviews of this file.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-10T08:13:52.153Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 39414
File: apps/meteor/app/api/server/v1/rooms.ts:1241-1297
Timestamp: 2026-03-10T08:13:52.153Z
Learning: In the RocketChat/Rocket.Chat OpenAPI migration PRs for endpoints under apps/meteor/app/api/server/v1/rooms.ts, the pattern `ajv.compile<void>({...})` is intentionally used for the 200 response schema even when the endpoint returns `{ success: true }`. This is an established convention across all migrated endpoints (rooms.leave, rooms.favorite, rooms.delete, rooms.muteUser, rooms.unmuteUser). Do not flag this as a type mismatch during reviews of these migration PRs.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-16T21:50:42.118Z
Learnt from: amitb0ra
Repo: RocketChat/Rocket.Chat PR: 39676
File: .changeset/migrate-users-register-openapi.md:3-3
Timestamp: 2026-03-16T21:50:42.118Z
Learning: In RocketChat/Rocket.Chat OpenAPI migration PRs, removing endpoint types and validators from `rocket.chat/rest-typings` (e.g., `UserRegisterParamsPOST`, `/v1/users.register` entry) is the *required* migration pattern per RocketChat/Rocket.Chat-Open-API#150 Rule 7 ("No More rest-typings or Manual Typings"). The endpoint type is re-exposed via a module augmentation `.d.ts` file in the consuming package (e.g., `packages/web-ui-registration/src/users-register.d.ts`). This is NOT a breaking change — the correct changeset bump for `rocket.chat/rest-typings` in this scenario is `minor`, not `major`. Do not flag this as a breaking change during OpenAPI migration reviews.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2025-11-04T16:49:19.107Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 37377
File: apps/meteor/ee/server/hooks/federation/index.ts:86-88
Timestamp: 2025-11-04T16:49:19.107Z
Learning: In Rocket.Chat's federation system (apps/meteor/ee/server/hooks/federation/), permission checks follow two distinct patterns: (1) User-initiated federation actions (creating rooms, adding users to federated rooms, joining from invites) should throw MeteorError to inform users they lack 'access-federation' permission. (2) Remote server-initiated federation events should silently skip/ignore when users lack permission. The beforeAddUserToRoom hook only executes for local user-initiated actions, so throwing an error there is correct. Remote federation events are handled separately by the federation Matrix package with silent skipping logic.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-16T22:56:54.500Z
Learnt from: KevLehman
Repo: RocketChat/Rocket.Chat PR: 39677
File: packages/models/src/helpers/omnichannel/agentStatus.ts:10-29
Timestamp: 2026-03-16T22:56:54.500Z
Learning: In `packages/models/src/helpers/omnichannel/agentStatus.ts` (PR `#39677`), the `queryStatusAgentOnline` function intentionally omits the `$or` offline-status guard for non-bot agents when `isLivechatEnabledWhenAgentIdle === true`. This is by design: the setting `Livechat_enabled_when_agent_idle` (`accept_chats_when_agent_idle`) means agents should receive chats even when idle/offline, so the offline filter must be removed in that path. Bots are always status-agnostic and are always included regardless of their online/offline status. Do not flag this as a bug.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-01-26T18:26:01.279Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38227
File: apps/meteor/app/api/server/router.ts:44-49
Timestamp: 2026-01-26T18:26:01.279Z
Learning: In apps/meteor/app/api/server/router.ts, when retrieving bodyParams and queryParams from the Hono context via c.get(), do not add defensive defaults (e.g., ?? {}). The code should fail fast if these parameters are missing, as endpoint handlers expect them to be present and breaking here helps surface parsing problems rather than hiding them.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-16T23:33:15.721Z
Learnt from: amitb0ra
Repo: RocketChat/Rocket.Chat PR: 39676
File: apps/meteor/app/api/server/v1/users.ts:862-869
Timestamp: 2026-03-16T23:33:15.721Z
Learning: In RocketChat/Rocket.Chat OpenAPI migration PRs (e.g., PR `#39676` for users.register in apps/meteor/app/api/server/v1/users.ts), calls to `this.parseJsonQuery()` inside migrated handlers are intentionally preserved without adding a corresponding `query` AJV schema to the route options. Adding query-param schemas for the `fields`/`sort`/`query` parameters consumed by `parseJsonQuery()` is a separate cross-cutting concern shared by many endpoints (e.g., users.create, users.update, users.list) and is explicitly out of scope for individual endpoint migration PRs. Do not flag the absence of a `query` schema for `parseJsonQuery()` usage as a violation of OpenAPI/AJV contract during migration reviews.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-03-19T13:59:40.678Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/uploads.ts:45-49
Timestamp: 2026-03-19T13:59:40.678Z
Learning: In `apps/meteor/app/apps/server/converters/uploads.ts`, the `room` async handler in `convertToApp` uses non-null assertions (`upload.rid!` and `result!`) intentionally. The data flow guarantees that any upload reaching this point must have a `rid`; if it does not, throwing an error is the desired behavior (fail-fast / data integrity guard). Do not flag these non-null assertions as unsafe during code review.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📚 Learning: 2026-02-23T17:53:06.802Z
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: During PR reviews that touch endpoint files under apps/meteor/app/api/server/v1, enforce strict scope: if a PR targets a specific endpoint (e.g., rooms.favorite), do not propose changes to unrelated endpoints (e.g., rooms.invite) unless maintainers explicitly request them. Focus feedback on the touched endpoint's behavior, API surface, and related tests; avoid broad cross-endpoint changes in the same PR unless requested.

Applied to files:

  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts

Comment thread apps/meteor/app/api/server/v1/chat.scheduleMessage.ts Outdated
Comment thread apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
Comment thread apps/meteor/server/cron/scheduledMessages.ts
Comment thread apps/meteor/server/cron/scheduledMessages.ts Outdated
@coderabbitai coderabbitai Bot added the type: feature Pull requests that introduces new feature label Apr 25, 2026
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: 3

🧹 Nitpick comments (3)
packages/i18n/src/locales/en.i18n.json (2)

4848-4856: Consolidate duplicated translation keys that differ only by case.

Schedule_Message/Schedule_message and View_scheduled_messages/View_Scheduled_Messages carry the same meaning and will drift over time. Prefer one canonical key per message and update call sites accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/i18n/src/locales/en.i18n.json` around lines 4848 - 4856, Consolidate
duplicated translation keys that differ only by case by choosing a single
canonical key for each message (e.g., pick either "Schedule_Message" or
"Schedule_message" and either "View_scheduled_messages" or
"View_Scheduled_Messages"), remove the duplicates from en.i18n.json, and update
all call sites that reference the removed keys to use the canonical key (search
for usages of "Schedule_Message", "Schedule_message", "View_scheduled_messages",
and "View_Scheduled_Messages" and replace them accordingly); ensure you keep the
existing string values unchanged and run tests or a translation usage grep to
confirm no references remain to the removed keys.

4858-4860: Prefer scoped keys over global generic status keys.

Keys like sent, cancelled, and failed are too broad in this global namespace and are easy to collide with unrelated features. Consider namespaced keys such as Scheduled_message_status_sent, etc.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/i18n/src/locales/en.i18n.json` around lines 4858 - 4860, The global
keys "sent", "cancelled", and "failed" in en.i18n.json are too generic—rename
them to scoped keys (e.g., "Scheduled_message_status_sent",
"Scheduled_message_status_cancelled", "Scheduled_message_status_failed") and
update all callers to use the new keys; search for any i18n lookups or
components referencing "sent", "cancelled", or "failed" and replace those
references (or add fallbacks) to the new scoped keys so existing UI text stays
correct.
apps/meteor/server/cron/scheduledMessages.ts (1)

57-70: Add scheduledRetryCount to IMessage; improve message object typing.

The as any cast on line 70 masks a missing type field. scheduledRetryCount is persisted to the database (line 97: $inc: { scheduledRetryCount: 1 }) but is not declared on IMessage, even though related fields like scheduled and scheduledAt are. Add scheduledRetryCount?: number to the IMessage interface.

Similarly, line 57's message: any defeats type checking on the dispatch payload. Since sendMessage already accepts any, this is lower priority, but importing the dispatch type or typing it as Partial<IMessage> & Pick<IMessage, 'msg' | 'rid'> would tighten coverage.

Per coding guidelines: "Write concise, technical TypeScript/JavaScript with accurate typing".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/server/cron/scheduledMessages.ts` around lines 57 - 70, The
IMessage interface is missing the optional field scheduledRetryCount; add
scheduledRetryCount?: number to IMessage so the DB $inc and reads are typed, and
replace the ad-hoc cast ((scheduledMessage as any).scheduledRetryCount || 0)
with a typed access (scheduledMessage.scheduledRetryCount ?? 0). Also tighten
the local message variable typing: change message: any to a safer type such as
Partial<IMessage> & Pick<IMessage, 'msg' | 'rid'> (or import the dispatch type
used by sendMessage) so the payload construction (msg, rid, tmid, alias, avatar,
emoji, attachments) is type-checked against IMessage while keeping sendMessage
compatibility.
🤖 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/server/cron/scheduledMessages.ts`:
- Around line 105-109: The function scheduledMessagesCron currently calls await
processScheduledMessages() immediately at startup which causes an unprotected,
synchronous catch-up pass; remove the eager startup invocation so the function
only registers the cron job via cronJobs.add('Process Scheduled Messages', '* *
* * *', async () => processScheduledMessages()) and rely solely on the scheduled
tick (i.e., delete the await processScheduledMessages() line and return the
cronJobs.add result directly).
- Around line 93-99: The retry logic currently re-sets scheduled:true and
increments scheduledRetryCount in Messages.updateOne for scheduledMessage._id
but leaves scheduledAt in the past, causing immediate reprocessing and burning
through maxRetries; update the database update to also push scheduledAt forward
using an exponential backoff based on the current scheduledRetryCount (e.g., now
+ 2^scheduledRetryCount minutes) so each retry is delayed, and continue
incrementing scheduledRetryCount (refer to Messages.updateOne,
scheduledMessage._id, scheduled, scheduledRetryCount, scheduledAt, maxRetries).
- Around line 43-55: The recoverable-error check in the catch block is broken
because it reads error.error (only present on Meteor.Error) and looks for a
non-existent 'error-room-not-found' code; update the logic in
scheduledMessages.ts (inside the catch handling for scheduledMessage) to derive
the error code from either error.error or error.message (e.g., const code =
error?.error ?? error?.message) and then check code against the recoverable
list, and replace 'error-room-not-found' with the correct 'error-invalid-room'
string so that archived/blocked/muted/invalid-room errors trigger the
Messages.updateOne retry branch instead of deleting the message.

---

Nitpick comments:
In `@apps/meteor/server/cron/scheduledMessages.ts`:
- Around line 57-70: The IMessage interface is missing the optional field
scheduledRetryCount; add scheduledRetryCount?: number to IMessage so the DB $inc
and reads are typed, and replace the ad-hoc cast ((scheduledMessage as
any).scheduledRetryCount || 0) with a typed access
(scheduledMessage.scheduledRetryCount ?? 0). Also tighten the local message
variable typing: change message: any to a safer type such as Partial<IMessage> &
Pick<IMessage, 'msg' | 'rid'> (or import the dispatch type used by sendMessage)
so the payload construction (msg, rid, tmid, alias, avatar, emoji, attachments)
is type-checked against IMessage while keeping sendMessage compatibility.

In `@packages/i18n/src/locales/en.i18n.json`:
- Around line 4848-4856: Consolidate duplicated translation keys that differ
only by case by choosing a single canonical key for each message (e.g., pick
either "Schedule_Message" or "Schedule_message" and either
"View_scheduled_messages" or "View_Scheduled_Messages"), remove the duplicates
from en.i18n.json, and update all call sites that reference the removed keys to
use the canonical key (search for usages of "Schedule_Message",
"Schedule_message", "View_scheduled_messages", and "View_Scheduled_Messages" and
replace them accordingly); ensure you keep the existing string values unchanged
and run tests or a translation usage grep to confirm no references remain to the
removed keys.
- Around line 4858-4860: The global keys "sent", "cancelled", and "failed" in
en.i18n.json are too generic—rename them to scoped keys (e.g.,
"Scheduled_message_status_sent", "Scheduled_message_status_cancelled",
"Scheduled_message_status_failed") and update all callers to use the new keys;
search for any i18n lookups or components referencing "sent", "cancelled", or
"failed" and replace those references (or add fallbacks) to the new scoped keys
so existing UI text stays correct.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 58101305-4f02-40eb-aa27-77c65666534e

📥 Commits

Reviewing files that changed from the base of the PR and between 02e39ac and f361b56.

📒 Files selected for processing (4)
  • .changeset/tasty-hoops-try.md
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
  • apps/meteor/server/cron/scheduledMessages.ts
  • packages/i18n/src/locales/en.i18n.json
✅ Files skipped from review due to trivial changes (1)
  • .changeset/tasty-hoops-try.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/meteor/app/api/server/v1/chat.scheduleMessage.ts
📜 Review details
🧰 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/server/cron/scheduledMessages.ts
🧠 Learnings (20)
📚 Learning: 2025-11-19T12:32:29.696Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 37547
File: packages/i18n/src/locales/en.i18n.json:634-634
Timestamp: 2025-11-19T12:32:29.696Z
Learning: Repo: RocketChat/Rocket.Chat
Context: i18n workflow
Learning: In this repository, new translation keys should be added to packages/i18n/src/locales/en.i18n.json only; other locale files are populated via the external translation pipeline and/or fall back to English. Do not request adding the same key to all locale files in future reviews.

Applied to files:

  • packages/i18n/src/locales/en.i18n.json
📚 Learning: 2025-11-19T18:20:07.720Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37419
File: packages/i18n/src/locales/en.i18n.json:918-921
Timestamp: 2025-11-19T18:20:07.720Z
Learning: Repo: RocketChat/Rocket.Chat — i18n/formatting
Learning: This repository uses a custom message formatting parser in UI blocks/messages; do not assume standard Markdown rules. For keys like Call_ended_bold, Call_not_answered_bold, Call_failed_bold, and Call_transferred_bold in packages/i18n/src/locales/en.i18n.json, retain the existing single-asterisk emphasis unless maintainers request otherwise.

Applied to files:

  • packages/i18n/src/locales/en.i18n.json
📚 Learning: 2026-04-18T12:32:53.425Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat PR: 38623
File: apps/meteor/app/lib/server/functions/cleanRoomHistory.ts:146-149
Timestamp: 2026-04-18T12:32:53.425Z
Learning: In `apps/meteor/app/lib/server/functions/cleanRoomHistory.ts` (PR `#38623`), the read-receipt cleanup (both `ReadReceipts.removeByMessageIds` and `ReadReceiptsArchive.removeByMessageIds`) is intentionally only performed in the limited prune path (`limit && selectedMessageIds`). The unlimited/delete-all path (`limit === 0`) deliberately skips cleaning up orphaned read receipts in both hot and cold storage — this is by design. Do not flag this as a bug or missing cleanup in future reviews.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-23T19:33:46.159Z
Learnt from: cardoso
Repo: RocketChat/Rocket.Chat PR: 39818
File: apps/meteor/client/hooks/usePruneWarningMessage.ts:45-56
Timestamp: 2026-03-23T19:33:46.159Z
Learning: In RocketChat/Rocket.Chat, cron expressions used by the retention policy (e.g., `RetentionPolicy_Advanced_Precision_Cron`) follow the convention of the `cron` npm package (outdated version), which uses **0-indexed months** (0 = January, 11 = December), not the standard 1-12 used in POSIX cron. The local `sendAt` function in `apps/meteor/client/hooks/usePruneWarningMessage.ts` intentionally replicates this 0-indexed behavior. Do NOT suggest adding `+1` to `date.getMonth()` in this file, as it would break tests and create inconsistency with the backend.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-11T22:04:20.529Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 39545
File: apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts:59-61
Timestamp: 2026-03-11T22:04:20.529Z
Learning: In `apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts`, the `msg.u._id === uid` early-return in the `streamNewMessage` handler is intentional: the "New messages" indicator is designed to notify about messages from other users only. Self-sent messages — including those sent from a different session/device — are always skipped, by design. Do not flag this as a multi-session regression.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-24T19:16:35.307Z
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:35.307Z
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.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-11T18:17:53.972Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39425
File: apps/meteor/client/lib/chats/flows/processMessageUploads.ts:112-119
Timestamp: 2026-03-11T18:17:53.972Z
Learning: In `apps/meteor/client/lib/chats/flows/processMessageUploads.ts`, when sending multiple file uploads, each file is confirmed via its own `/rooms.mediaConfirm/${rid}/${fileId}` call and produces a separate message. Only the first file's confirm payload carries the composed message text (`msg`); all subsequent files receive `msg: ''`. This one-message-per-file behavior is intentional by design — do not flag it as a bug or suggest batching into a single message.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-09T18:39:21.178Z
Learnt from: Harxhit
Repo: RocketChat/Rocket.Chat PR: 39476
File: apps/meteor/server/methods/addAllUserToRoom.ts:0-0
Timestamp: 2026-03-09T18:39:21.178Z
Learning: In apps/meteor/server/methods/addAllUserToRoom.ts, the implementation uses a single cursor pass (Users.find(userFilter).batchSize(100)) that collects both the full user objects (collectedUsers: IUser[]) and their usernames (usernames: string[]) in one iteration. `beforeAddUserToRoom` is then called once with the full usernames batch (preserving batch-validation semantics), and the subsequent subscription/message processing loop iterates over the same stable `collectedUsers` array — no second DB query is made. This avoids any race condition between validation and processing while preserving the original batch-validation behavior.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.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/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-17T23:32:07.223Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/messages.ts:348-352
Timestamp: 2026-04-17T23:32:07.223Z
Learning: In `apps/meteor/app/apps/server/converters/messages.ts`, the `timestamp` handler inside `_convertAttachmentsToApp` uses a non-null assertion (`attachment.ts!`) intentionally. The `ts` property on `MessageAttachment` is optional only to accommodate MessageAttachment creation-time scenarios; by the time `_convertAttachmentsToApp` is called, the message has already been stored and the attachment is guaranteed to have a `ts` value. Do not flag this non-null assertion as unsafe during code review.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-16T11:50:18.066Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39657
File: apps/meteor/tests/end-to-end/apps/app-resolve-visitor.ts:18-21
Timestamp: 2026-03-16T11:50:18.066Z
Learning: In `apps/meteor/tests/end-to-end/apps/` (and Livechat E2E tests broadly), calling `createAgent()` and `makeAgentAvailable()` immediately after `updateSetting('Livechat_enabled', true)` — without any intermediate sleep — is an established, non-flaky pattern used across 10+ tests in the codebase. Do not flag this sequence as a potential race condition or suggest adding a delay between them.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.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/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-23T18:10:55.887Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 39857
File: apps/meteor/app/api/server/middlewares/metrics.ts:25-57
Timestamp: 2026-04-23T18:10:55.887Z
Learning: In RocketChat/Rocket.Chat, the route handler action wrapper in `apps/meteor/app/api/server/ApiClass.ts` (around line 892) contains a `catch` block that intercepts all route-handler errors and converts them into proper HTTP API response objects (e.g., `api.failure`, `api.unauthorized`, `api.tooManyRequests`). As a result, `next()` in Hono middleware (such as `metricsMiddleware` in `apps/meteor/app/api/server/middlewares/metrics.ts`) will never throw. Do not flag missing try/finally guards around `await next()` calls in this middleware for gauge/counter decrement safety, as the execution flow is never disrupted by route-level errors.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.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/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-23T18:14:24.851Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 39857
File: apps/meteor/app/lib/server/lib/debug.js:87-91
Timestamp: 2026-04-23T18:14:24.851Z
Learning: In `apps/meteor/app/lib/server/lib/debug.js`, the `wrapMethods` function wraps Meteor DDP method handlers inside a `tracerActiveSpan` callback. The `end()` (summary timer) and `endHistogram()` (histogram timer) calls are placed after `await originalHandler.apply(this, originalArgs)` without a try/finally guard. This is intentional and safe: errors thrown by Meteor method handlers are caught by the route-handler action wrapper in `apps/meteor/app/api/server/ApiClass.ts` (around line 892-893), which converts them into proper response objects, so execution flow is never disrupted and the timer calls are always reached. Do not flag the missing try/finally as a bug for these timer calls in `debug.js`.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-12T15:39:28.416Z
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.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-23T13:34:53.287Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 40269
File: packages/apps-engine/src/server/accessors/Notifier.ts:19-19
Timestamp: 2026-04-23T13:34:53.287Z
Learning: In `packages/apps-engine/src/server/accessors/Notifier.ts`, the `as IUser` cast on `await this.userBridge.doGetAppUser(this.appId)` in both `notifyUser` (line ~19) and `notifyRoom` (line ~29) is intentional — the app user is guaranteed to exist at that point in the execution flow. Do not flag this as an unsafe cast. Any improvement to add a defensive guard is deferred to a future PR.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-19T13:59:40.678Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/uploads.ts:45-49
Timestamp: 2026-03-19T13:59:40.678Z
Learning: In `apps/meteor/app/apps/server/converters/uploads.ts`, the `room` async handler in `convertToApp` uses non-null assertions (`upload.rid!` and `result!`) intentionally. The data flow guarantees that any upload reaching this point must have a `rid`; if it does not, throwing an error is the desired behavior (fail-fast / data integrity guard). Do not flag these non-null assertions as unsafe during code review.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
🔇 Additional comments (1)
apps/meteor/server/cron/scheduledMessages.ts (1)

67-68: Duplicate delivery if Messages.deleteOne fails after a successful sendMessage.

If sendMessage succeeds and the subsequent deleteOne throws (transient Mongo failure, step-down, etc.), control falls into the outer catch at line 69 which re-sets scheduled: true and increments scheduledRetryCount. Since scheduledAt is unchanged, the next tick re-claims the same document and dispatches the message a second time. This was raised on a previous commit and is still present — consider deleting (or marking dispatched) before sendMessage, or guarding the outer catch from running after a successful send.

Comment thread apps/meteor/server/cron/scheduledMessages.ts Outdated
Comment thread apps/meteor/server/cron/scheduledMessages.ts
@coderabbitai coderabbitai Bot removed the type: feature Pull requests that introduces new feature label Apr 25, 2026
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: 3

🤖 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/server/cron/scheduledMessages.ts`:
- Around line 102-104: The backoff uses the stale variable now (captured before
the per-message loop) when computing newScheduledAt, so delays can be too short
or in the past; update the calculation in the catch block to use the current
time at scheduling (e.g., Date.now() or new Date()) together with retryCount to
compute newScheduledAt, and remove the inline comment about exponential backoff;
locate and change the code that computes newScheduledAt (references: now,
retryCount, newScheduledAt in scheduledMessages.ts) to use the current timestamp
instead of now.
- Around line 75-77: Remove the pre-send deletion that calls
Messages.deleteOne({ _id: scheduledMessage._id }) before sendMessage; instead,
rely on the earlier atomic claim that set scheduled: false to prevent re-pick,
call sendMessage(user, message, room, {}), and only after a successful send
remove the scheduled document (i.e., move the deleteOne to after sendMessage
succeeds); also drop the inline comment about deleting before send. This ensures
transient sendMessage failures trigger the existing catch-path logic (which uses
Messages.updateOne / Messages.deleteOne) and prevents silent loss of the drafted
message.
- Around line 43-63: The recoverable branch in scheduledMessages.ts currently
only sets scheduled:true causing infinite immediate retries; update that branch
(where Messages.updateOne is called for recoverable errors) to mirror the outer
catch behavior: log a warning via SystemLogger.warn including err, messageId,
roomId, userId; compute and set a new scheduledAt using exponential backoff
based on scheduledRetryCount (increment scheduledRetryCount in the same update);
if the retry count exceeds the cap, dead-letter the message with
Messages.deleteOne (and log that action) instead of rescheduling. Ensure you
update the same document fields (scheduledAt and scheduledRetryCount) atomically
in the Messages.updateOne call and include contextual logging similar to the
non-recoverable path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3340a1e2-2a8d-4693-a3ef-14d010548c77

📥 Commits

Reviewing files that changed from the base of the PR and between f361b56 and 8823ec4.

📒 Files selected for processing (1)
  • apps/meteor/server/cron/scheduledMessages.ts
📜 Review details
🧰 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/server/cron/scheduledMessages.ts
🧠 Learnings (20)
📚 Learning: 2026-04-18T12:32:53.425Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat PR: 38623
File: apps/meteor/app/lib/server/functions/cleanRoomHistory.ts:146-149
Timestamp: 2026-04-18T12:32:53.425Z
Learning: In `apps/meteor/app/lib/server/functions/cleanRoomHistory.ts` (PR `#38623`), the read-receipt cleanup (both `ReadReceipts.removeByMessageIds` and `ReadReceiptsArchive.removeByMessageIds`) is intentionally only performed in the limited prune path (`limit && selectedMessageIds`). The unlimited/delete-all path (`limit === 0`) deliberately skips cleaning up orphaned read receipts in both hot and cold storage — this is by design. Do not flag this as a bug or missing cleanup in future reviews.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-23T19:33:46.159Z
Learnt from: cardoso
Repo: RocketChat/Rocket.Chat PR: 39818
File: apps/meteor/client/hooks/usePruneWarningMessage.ts:45-56
Timestamp: 2026-03-23T19:33:46.159Z
Learning: In RocketChat/Rocket.Chat, cron expressions used by the retention policy (e.g., `RetentionPolicy_Advanced_Precision_Cron`) follow the convention of the `cron` npm package (outdated version), which uses **0-indexed months** (0 = January, 11 = December), not the standard 1-12 used in POSIX cron. The local `sendAt` function in `apps/meteor/client/hooks/usePruneWarningMessage.ts` intentionally replicates this 0-indexed behavior. Do NOT suggest adding `+1` to `date.getMonth()` in this file, as it would break tests and create inconsistency with the backend.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-11T22:04:20.529Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 39545
File: apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts:59-61
Timestamp: 2026-03-11T22:04:20.529Z
Learning: In `apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts`, the `msg.u._id === uid` early-return in the `streamNewMessage` handler is intentional: the "New messages" indicator is designed to notify about messages from other users only. Self-sent messages — including those sent from a different session/device — are always skipped, by design. Do not flag this as a multi-session regression.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-24T19:16:35.307Z
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:35.307Z
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.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-17T23:32:07.223Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/messages.ts:348-352
Timestamp: 2026-04-17T23:32:07.223Z
Learning: In `apps/meteor/app/apps/server/converters/messages.ts`, the `timestamp` handler inside `_convertAttachmentsToApp` uses a non-null assertion (`attachment.ts!`) intentionally. The `ts` property on `MessageAttachment` is optional only to accommodate MessageAttachment creation-time scenarios; by the time `_convertAttachmentsToApp` is called, the message has already been stored and the attachment is guaranteed to have a `ts` value. Do not flag this non-null assertion as unsafe during code review.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-11T18:17:53.972Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39425
File: apps/meteor/client/lib/chats/flows/processMessageUploads.ts:112-119
Timestamp: 2026-03-11T18:17:53.972Z
Learning: In `apps/meteor/client/lib/chats/flows/processMessageUploads.ts`, when sending multiple file uploads, each file is confirmed via its own `/rooms.mediaConfirm/${rid}/${fileId}` call and produces a separate message. Only the first file's confirm payload carries the composed message text (`msg`); all subsequent files receive `msg: ''`. This one-message-per-file behavior is intentional by design — do not flag it as a bug or suggest batching into a single message.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.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/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-09T18:39:21.178Z
Learnt from: Harxhit
Repo: RocketChat/Rocket.Chat PR: 39476
File: apps/meteor/server/methods/addAllUserToRoom.ts:0-0
Timestamp: 2026-03-09T18:39:21.178Z
Learning: In apps/meteor/server/methods/addAllUserToRoom.ts, the implementation uses a single cursor pass (Users.find(userFilter).batchSize(100)) that collects both the full user objects (collectedUsers: IUser[]) and their usernames (usernames: string[]) in one iteration. `beforeAddUserToRoom` is then called once with the full usernames batch (preserving batch-validation semantics), and the subsequent subscription/message processing loop iterates over the same stable `collectedUsers` array — no second DB query is made. This avoids any race condition between validation and processing while preserving the original batch-validation behavior.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-16T11:50:18.066Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39657
File: apps/meteor/tests/end-to-end/apps/app-resolve-visitor.ts:18-21
Timestamp: 2026-03-16T11:50:18.066Z
Learning: In `apps/meteor/tests/end-to-end/apps/` (and Livechat E2E tests broadly), calling `createAgent()` and `makeAgentAvailable()` immediately after `updateSetting('Livechat_enabled', true)` — without any intermediate sleep — is an established, non-flaky pattern used across 10+ tests in the codebase. Do not flag this sequence as a potential race condition or suggest adding a delay between them.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.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/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-23T18:10:55.887Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 39857
File: apps/meteor/app/api/server/middlewares/metrics.ts:25-57
Timestamp: 2026-04-23T18:10:55.887Z
Learning: In RocketChat/Rocket.Chat, the route handler action wrapper in `apps/meteor/app/api/server/ApiClass.ts` (around line 892) contains a `catch` block that intercepts all route-handler errors and converts them into proper HTTP API response objects (e.g., `api.failure`, `api.unauthorized`, `api.tooManyRequests`). As a result, `next()` in Hono middleware (such as `metricsMiddleware` in `apps/meteor/app/api/server/middlewares/metrics.ts`) will never throw. Do not flag missing try/finally guards around `await next()` calls in this middleware for gauge/counter decrement safety, as the execution flow is never disrupted by route-level errors.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.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/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-23T18:14:24.851Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 39857
File: apps/meteor/app/lib/server/lib/debug.js:87-91
Timestamp: 2026-04-23T18:14:24.851Z
Learning: In `apps/meteor/app/lib/server/lib/debug.js`, the `wrapMethods` function wraps Meteor DDP method handlers inside a `tracerActiveSpan` callback. The `end()` (summary timer) and `endHistogram()` (histogram timer) calls are placed after `await originalHandler.apply(this, originalArgs)` without a try/finally guard. This is intentional and safe: errors thrown by Meteor method handlers are caught by the route-handler action wrapper in `apps/meteor/app/api/server/ApiClass.ts` (around line 892-893), which converts them into proper response objects, so execution flow is never disrupted and the timer calls are always reached. Do not flag the missing try/finally as a bug for these timer calls in `debug.js`.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-12T15:39:28.416Z
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.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-23T13:34:53.287Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 40269
File: packages/apps-engine/src/server/accessors/Notifier.ts:19-19
Timestamp: 2026-04-23T13:34:53.287Z
Learning: In `packages/apps-engine/src/server/accessors/Notifier.ts`, the `as IUser` cast on `await this.userBridge.doGetAppUser(this.appId)` in both `notifyUser` (line ~19) and `notifyRoom` (line ~29) is intentional — the app user is guaranteed to exist at that point in the execution flow. Do not flag this as an unsafe cast. Any improvement to add a defensive guard is deferred to a future PR.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-19T13:59:40.678Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/uploads.ts:45-49
Timestamp: 2026-03-19T13:59:40.678Z
Learning: In `apps/meteor/app/apps/server/converters/uploads.ts`, the `room` async handler in `convertToApp` uses non-null assertions (`upload.rid!` and `result!`) intentionally. The data flow guarantees that any upload reaching this point must have a `rid`; if it does not, throwing an error is the desired behavior (fail-fast / data integrity guard). Do not flag these non-null assertions as unsafe during code review.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2025-11-04T16:49:19.107Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 37377
File: apps/meteor/ee/server/hooks/federation/index.ts:86-88
Timestamp: 2025-11-04T16:49:19.107Z
Learning: In Rocket.Chat's federation system (apps/meteor/ee/server/hooks/federation/), permission checks follow two distinct patterns: (1) User-initiated federation actions (creating rooms, adding users to federated rooms, joining from invites) should throw MeteorError to inform users they lack 'access-federation' permission. (2) Remote server-initiated federation events should silently skip/ignore when users lack permission. The beforeAddUserToRoom hook only executes for local user-initiated actions, so throwing an error there is correct. Remote federation events are handled separately by the federation Matrix package with silent skipping logic.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-24T19:36:55.089Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 38493
File: apps/meteor/tests/e2e/page-objects/fragments/home-content.ts:60-82
Timestamp: 2026-02-24T19:36:55.089Z
Learning: In RocketChat/Rocket.Chat e2e tests (apps/meteor/tests/e2e/page-objects/fragments/home-content.ts), thread message preview listitems do not have aria-roledescription="message", so lastThreadMessagePreview locator cannot be scoped to messageListItems (which filters for aria-roledescription="message"). It should remain scoped to page.getByRole('listitem') or mainMessageList.getByRole('listitem').

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts

Comment thread apps/meteor/server/cron/scheduledMessages.ts
Comment thread apps/meteor/server/cron/scheduledMessages.ts Outdated
Comment thread apps/meteor/server/cron/scheduledMessages.ts Outdated
@coderabbitai coderabbitai Bot added the type: feature Pull requests that introduces new feature label Apr 25, 2026
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: 1

🧹 Nitpick comments (2)
apps/meteor/server/cron/scheduledMessages.ts (2)

51-51: Drop the as any casts and message: any — extend the model type instead.

(scheduledMessage as any).scheduledRetryCount (lines 51, 106) and const message: any (line 92) defeat TypeScript on the very fields this PR introduces. Since the PR is adding scheduled, scheduledAt, and scheduledRetryCount to the message model already, declare them as optional on IMessage (or a ScheduledMessage extension) so the cron reads them with proper typing, and type the outbound payload as Partial<IMessage> (or a dedicated SendMessagePayload).

♻️ Sketch
-			const retryCount = ((scheduledMessage as any).scheduledRetryCount || 0) + 1;
+			const retryCount = (scheduledMessage.scheduledRetryCount ?? 0) + 1;
-			const message: any = {
+			const message: Partial<IMessage> = {
 				msg: scheduledMessage.msg,
 				rid: scheduledMessage.rid,
 				...
 			};

As per coding guidelines (**/*.{ts,tsx,js}: "Write concise, technical TypeScript/JavaScript with accurate typing").

Also applies to: 92-92, 106-106

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/server/cron/scheduledMessages.ts` at line 51, The cron file is
using unsafe any casts for scheduled fields—replace them by extending the
message model: add optional properties scheduled?: boolean, scheduledAt?:
number|string, and scheduledRetryCount?: number to IMessage (or create a
ScheduledMessage interface extending IMessage) so scheduledMessage can be
properly typed and you can remove (scheduledMessage as any). Also change the
outbound payload variable currently declared as message: any to a typed payload
such as Partial<IMessage> or a dedicated SendMessagePayload type, and update
usages in functions like the retry logic and send path (references:
scheduledMessage, scheduledRetryCount, scheduledAt, scheduled) to use the new
typed interface instead of any.

50-78: Duplicated retry/backoff/dead-letter logic — extract a helper.

The recoverable-auth branch (lines 50–78) and the sendMessage failure branch (lines 105–143) implement essentially identical logic: bump scheduledRetryCount, dead-letter once >= maxRetries (with the same SystemLogger.error shape), otherwise compute 2^retryCount minutes of backoff and updateOne the same three fields. Extracting a small rescheduleOrDropFailedMessage(scheduledMessage, error, logMsg) (or two thin wrappers) would remove ~40 lines of near-identical code, ensure both paths stay in sync (e.g., next time you tweak maxRetries or backoff cap), and make the per-message control flow easier to follow.

Also worth considering: cap the backoff (e.g., Math.min(2 ** retryCount, 30) minutes) so retryCount=4 doesn't push the next attempt 16 minutes out only to be dead-lettered on the 5th anyway.

Also applies to: 105-143

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/server/cron/scheduledMessages.ts` around lines 50 - 78, Extract
the duplicated retry/dead-letter/backoff logic into a helper (e.g.,
rescheduleOrDropFailedMessage(scheduledMessage, error, logMsg)) and call it from
both the recoverable-auth branch and the sendMessage failure branch; the helper
should read and increment scheduledRetryCount on the passed scheduledMessage,
compare it to a single maxRetries constant, call SystemLogger.error with the
same shape (msg, err, messageId, roomId, userId, retryCount) and delete the
message via Messages.deleteOne when max reached, otherwise compute a capped
backoff (e.g., backoffMins = Math.min(2 ** retryCount, 30)), set scheduledAt,
scheduledRetryCount and scheduled via Messages.updateOne, and return or throw as
appropriate so the callers remain simple and consistent.
🤖 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/server/cron/scheduledMessages.ts`:
- Around line 34-41: The user-not-found branch after Users.findOneById currently
flips the message back to scheduled: true and leaves scheduledAt in the past,
causing perpetual retries; update this branch in scheduledMessages.ts to treat a
missing user as non-recoverable by applying the same dead-letter or
retry/backoff logic used in canSendMessageAsync/sendMessage: either mark the
message as dead-lettered when retries >= maxRetries (set appropriate
status/fields and log via SystemLogger.error including context), or increment
the retry counter and set scheduledAt with exponential backoff (same
algorithm/fields used elsewhere) and persist via Messages.updateOne for
consistency; reference scheduledMessage._id,
scheduledMessage.scheduledAt/retries, Messages.updateOne, Users.findOneById,
maxRetries, canSendMessageAsync, sendMessage and SystemLogger.error when
locating and implementing the fix.

---

Nitpick comments:
In `@apps/meteor/server/cron/scheduledMessages.ts`:
- Line 51: The cron file is using unsafe any casts for scheduled fields—replace
them by extending the message model: add optional properties scheduled?:
boolean, scheduledAt?: number|string, and scheduledRetryCount?: number to
IMessage (or create a ScheduledMessage interface extending IMessage) so
scheduledMessage can be properly typed and you can remove (scheduledMessage as
any). Also change the outbound payload variable currently declared as message:
any to a typed payload such as Partial<IMessage> or a dedicated
SendMessagePayload type, and update usages in functions like the retry logic and
send path (references: scheduledMessage, scheduledRetryCount, scheduledAt,
scheduled) to use the new typed interface instead of any.
- Around line 50-78: Extract the duplicated retry/dead-letter/backoff logic into
a helper (e.g., rescheduleOrDropFailedMessage(scheduledMessage, error, logMsg))
and call it from both the recoverable-auth branch and the sendMessage failure
branch; the helper should read and increment scheduledRetryCount on the passed
scheduledMessage, compare it to a single maxRetries constant, call
SystemLogger.error with the same shape (msg, err, messageId, roomId, userId,
retryCount) and delete the message via Messages.deleteOne when max reached,
otherwise compute a capped backoff (e.g., backoffMins = Math.min(2 **
retryCount, 30)), set scheduledAt, scheduledRetryCount and scheduled via
Messages.updateOne, and return or throw as appropriate so the callers remain
simple and consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20c6e844-ca43-419c-aaf7-850d19bf2c8c

📥 Commits

Reviewing files that changed from the base of the PR and between 8823ec4 and 402768d.

📒 Files selected for processing (1)
  • apps/meteor/server/cron/scheduledMessages.ts
📜 Review details
🧰 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/server/cron/scheduledMessages.ts
🧠 Learnings (20)
📚 Learning: 2026-03-23T19:33:46.159Z
Learnt from: cardoso
Repo: RocketChat/Rocket.Chat PR: 39818
File: apps/meteor/client/hooks/usePruneWarningMessage.ts:45-56
Timestamp: 2026-03-23T19:33:46.159Z
Learning: In RocketChat/Rocket.Chat, cron expressions used by the retention policy (e.g., `RetentionPolicy_Advanced_Precision_Cron`) follow the convention of the `cron` npm package (outdated version), which uses **0-indexed months** (0 = January, 11 = December), not the standard 1-12 used in POSIX cron. The local `sendAt` function in `apps/meteor/client/hooks/usePruneWarningMessage.ts` intentionally replicates this 0-indexed behavior. Do NOT suggest adding `+1` to `date.getMonth()` in this file, as it would break tests and create inconsistency with the backend.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-18T12:32:53.425Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat PR: 38623
File: apps/meteor/app/lib/server/functions/cleanRoomHistory.ts:146-149
Timestamp: 2026-04-18T12:32:53.425Z
Learning: In `apps/meteor/app/lib/server/functions/cleanRoomHistory.ts` (PR `#38623`), the read-receipt cleanup (both `ReadReceipts.removeByMessageIds` and `ReadReceiptsArchive.removeByMessageIds`) is intentionally only performed in the limited prune path (`limit && selectedMessageIds`). The unlimited/delete-all path (`limit === 0`) deliberately skips cleaning up orphaned read receipts in both hot and cold storage — this is by design. Do not flag this as a bug or missing cleanup in future reviews.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-11T22:04:20.529Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 39545
File: apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts:59-61
Timestamp: 2026-03-11T22:04:20.529Z
Learning: In `apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts`, the `msg.u._id === uid` early-return in the `streamNewMessage` handler is intentional: the "New messages" indicator is designed to notify about messages from other users only. Self-sent messages — including those sent from a different session/device — are always skipped, by design. Do not flag this as a multi-session regression.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-17T23:32:07.223Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/messages.ts:348-352
Timestamp: 2026-04-17T23:32:07.223Z
Learning: In `apps/meteor/app/apps/server/converters/messages.ts`, the `timestamp` handler inside `_convertAttachmentsToApp` uses a non-null assertion (`attachment.ts!`) intentionally. The `ts` property on `MessageAttachment` is optional only to accommodate MessageAttachment creation-time scenarios; by the time `_convertAttachmentsToApp` is called, the message has already been stored and the attachment is guaranteed to have a `ts` value. Do not flag this non-null assertion as unsafe during code review.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-09T18:39:21.178Z
Learnt from: Harxhit
Repo: RocketChat/Rocket.Chat PR: 39476
File: apps/meteor/server/methods/addAllUserToRoom.ts:0-0
Timestamp: 2026-03-09T18:39:21.178Z
Learning: In apps/meteor/server/methods/addAllUserToRoom.ts, the implementation uses a single cursor pass (Users.find(userFilter).batchSize(100)) that collects both the full user objects (collectedUsers: IUser[]) and their usernames (usernames: string[]) in one iteration. `beforeAddUserToRoom` is then called once with the full usernames batch (preserving batch-validation semantics), and the subsequent subscription/message processing loop iterates over the same stable `collectedUsers` array — no second DB query is made. This avoids any race condition between validation and processing while preserving the original batch-validation behavior.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-24T19:16:35.307Z
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:35.307Z
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.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-11T18:17:53.972Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39425
File: apps/meteor/client/lib/chats/flows/processMessageUploads.ts:112-119
Timestamp: 2026-03-11T18:17:53.972Z
Learning: In `apps/meteor/client/lib/chats/flows/processMessageUploads.ts`, when sending multiple file uploads, each file is confirmed via its own `/rooms.mediaConfirm/${rid}/${fileId}` call and produces a separate message. Only the first file's confirm payload carries the composed message text (`msg`); all subsequent files receive `msg: ''`. This one-message-per-file behavior is intentional by design — do not flag it as a bug or suggest batching into a single message.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.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/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-16T11:50:18.066Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39657
File: apps/meteor/tests/end-to-end/apps/app-resolve-visitor.ts:18-21
Timestamp: 2026-03-16T11:50:18.066Z
Learning: In `apps/meteor/tests/end-to-end/apps/` (and Livechat E2E tests broadly), calling `createAgent()` and `makeAgentAvailable()` immediately after `updateSetting('Livechat_enabled', true)` — without any intermediate sleep — is an established, non-flaky pattern used across 10+ tests in the codebase. Do not flag this sequence as a potential race condition or suggest adding a delay between them.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.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/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-23T18:10:55.887Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 39857
File: apps/meteor/app/api/server/middlewares/metrics.ts:25-57
Timestamp: 2026-04-23T18:10:55.887Z
Learning: In RocketChat/Rocket.Chat, the route handler action wrapper in `apps/meteor/app/api/server/ApiClass.ts` (around line 892) contains a `catch` block that intercepts all route-handler errors and converts them into proper HTTP API response objects (e.g., `api.failure`, `api.unauthorized`, `api.tooManyRequests`). As a result, `next()` in Hono middleware (such as `metricsMiddleware` in `apps/meteor/app/api/server/middlewares/metrics.ts`) will never throw. Do not flag missing try/finally guards around `await next()` calls in this middleware for gauge/counter decrement safety, as the execution flow is never disrupted by route-level errors.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.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/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-23T18:14:24.851Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 39857
File: apps/meteor/app/lib/server/lib/debug.js:87-91
Timestamp: 2026-04-23T18:14:24.851Z
Learning: In `apps/meteor/app/lib/server/lib/debug.js`, the `wrapMethods` function wraps Meteor DDP method handlers inside a `tracerActiveSpan` callback. The `end()` (summary timer) and `endHistogram()` (histogram timer) calls are placed after `await originalHandler.apply(this, originalArgs)` without a try/finally guard. This is intentional and safe: errors thrown by Meteor method handlers are caught by the route-handler action wrapper in `apps/meteor/app/api/server/ApiClass.ts` (around line 892-893), which converts them into proper response objects, so execution flow is never disrupted and the timer calls are always reached. Do not flag the missing try/finally as a bug for these timer calls in `debug.js`.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-12T15:39:28.416Z
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.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-23T13:34:53.287Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 40269
File: packages/apps-engine/src/server/accessors/Notifier.ts:19-19
Timestamp: 2026-04-23T13:34:53.287Z
Learning: In `packages/apps-engine/src/server/accessors/Notifier.ts`, the `as IUser` cast on `await this.userBridge.doGetAppUser(this.appId)` in both `notifyUser` (line ~19) and `notifyRoom` (line ~29) is intentional — the app user is guaranteed to exist at that point in the execution flow. Do not flag this as an unsafe cast. Any improvement to add a defensive guard is deferred to a future PR.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-04-20T17:11:59.452Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 40225
File: apps/meteor/ee/server/apps/communication/endpoints/appLogsHandler.ts:55-71
Timestamp: 2026-04-20T17:11:59.452Z
Learning: In `apps/meteor/ee/server/apps/communication/endpoints/appLogsHandler.ts`, the concern about an empty `?appId=` query param bypassing the truthy check and overriding the path `appId` in the `makeAppLogsQuery` spread is not relevant. The AJV query schema (`isAppLogsProps`) validates and rejects invalid/empty `appId` values before the action handler is reached, making the in-handler guard sufficient as-is. Do not flag this pattern as a vulnerability in future reviews of this file.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-16T22:56:54.500Z
Learnt from: KevLehman
Repo: RocketChat/Rocket.Chat PR: 39677
File: packages/models/src/helpers/omnichannel/agentStatus.ts:10-29
Timestamp: 2026-03-16T22:56:54.500Z
Learning: In `packages/models/src/helpers/omnichannel/agentStatus.ts` (PR `#39677`), the `queryStatusAgentOnline` function intentionally omits the `$or` offline-status guard for non-bot agents when `isLivechatEnabledWhenAgentIdle === true`. This is by design: the setting `Livechat_enabled_when_agent_idle` (`accept_chats_when_agent_idle`) means agents should receive chats even when idle/offline, so the offline filter must be removed in that path. Bots are always status-agnostic and are always included regardless of their online/offline status. Do not flag this as a bug.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-03-06T18:09:17.867Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/DateTimeFormats.spec.tsx:20-23
Timestamp: 2026-03-06T18:09:17.867Z
Learning: In the RocketChat/Rocket.Chat gazzodown package (`packages/gazzodown`), tests are intended to run under the UTC timezone, but as of PR `#39397` this is NOT yet explicitly enforced in `jest.config.ts` or the `package.json` test scripts (which just run `jest` without `TZ=UTC`). To make timezone-sensitive snapshot tests reliable across all environments, `TZ=UTC` should be added to the test scripts in `package.json` or to `jest.config.ts` via `testEnvironmentOptions.timezone`. Without explicit UTC enforcement, snapshot tests involving date-fns formatted output or `toLocaleString()` will fail for contributors in non-UTC timezones.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • apps/meteor/server/cron/scheduledMessages.ts

Comment thread apps/meteor/server/cron/scheduledMessages.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community type: feature Pull requests that introduces new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant