Skip to content

chore: onSettingChanged hook for services#38615

Merged
kodiakhq[bot] merged 7 commits intodevelopfrom
chore/new-setting-hook-svr
Feb 13, 2026
Merged

chore: onSettingChanged hook for services#38615
kodiakhq[bot] merged 7 commits intodevelopfrom
chore/new-setting-hook-svr

Conversation

@KevLehman
Copy link
Member

@KevLehman KevLehman commented Feb 11, 2026

Proposed changes (including videos or screenshots)

Issue(s)

https://rocketchat.atlassian.net/browse/CORE-1820

Steps to test or reproduce

Further comments

https://github.com/RocketChat/adrs/pull/166

Summary by CodeRabbit

  • Refactor
    • Settings are now handled per-setting with explicit change listeners, simplifying live configuration updates and exposing a new per-setting listener API.
  • Bug Fixes
    • Runtime updates to federation flags and account login expiration now apply more consistently without restarts.
  • Tests
    • Test mocks updated to support the new per-setting listener behavior.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Feb 11, 2026

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

@changeset-bot
Copy link

changeset-bot bot commented Feb 11, 2026

⚠️ No Changeset found

Latest commit: 86a751a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 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 a new onSettingChanged API to ServiceClass for subscribing to individual setting updates and replaces several onEvent('watch.settings', ...) listeners with per-setting handlers that validate setting.value, optionally ignore specified client actions, and update local service state.

Changes

Cohort / File(s) Summary
Core API
packages/core-services/src/types/ServiceClass.ts, packages/core-services/src/events/Events.ts
Introduce onSettingChanged(settingId, cb, ignoreActions?) on IServiceClass/ServiceClass, add internal settings emitter and listener wiring, and export ClientAction for typing.
Service Consumers
apps/meteor/ee/server/local-services/instance/service.ts, ee/apps/account-service/src/Account.ts, ee/packages/federation-matrix/src/FederationMatrix.ts
Replace broad onEvent('watch.settings', ...) usage with per-setting onSettingChanged handlers; handlers now inspect setting.value, perform type checks, and update service state per setting (FederationMatrix split into multiple handlers).
Tests / Mocks
apps/meteor/tests/unit/server/services/instance/service.tests.ts
Add onSettingChanged sinon stub to the test ServiceClass mock to reflect the new API surface.

Sequence Diagram(s)

sequenceDiagram
  participant EventBus as watch.settings
  participant ServiceClass
  participant ServiceImpl as Service (Instance/Account/Federation)
  participant Settings as SettingStore

  EventBus->>ServiceClass: emit { clientAction, setting }
  ServiceClass->>ServiceClass: route event to per-setting listeners
  ServiceClass->>ServiceImpl: invoke onSettingChanged callback({ clientAction, setting })
  ServiceImpl->>Settings: inspect setting.value (type check / ignoreActions)
  ServiceImpl-->>ServiceImpl: update internal state when valid
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I twitch when settings start to sing,
A hop, a route, a careful spring.
Each listener tuned, each value checked,
I nudge the state where bits connect.
Hooray — the meadow's systems sync.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: onSettingChanged hook for services' clearly summarizes the main change—introducing a new onSettingChanged hook API for services, which aligns perfectly with the extensive refactoring across multiple files to implement this feature.
Linked Issues check ✅ Passed The pull request fully addresses CORE-1820 by introducing onSettingChanged API, replacing onEvent('watch.settings') calls, and providing an easier-to-use alternative to the current onEvent approach across multiple service implementations.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the onSettingChanged API and its usage: core service infrastructure updates, API endpoint implementations, test mocks, and consumer updates are all aligned with CORE-1820 objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 11, 2026

📦 Docker Image Size Report

📈 Changes

Service Current Baseline Change Percent
sum of all images 1.1GiB 1.1GiB +11MiB
rocketchat 360MiB 349MiB +11MiB
omnichannel-transcript-service 134MiB 134MiB -389B
queue-worker-service 134MiB 134MiB +787B
ddp-streamer-service 128MiB 128MiB +1.1KiB
account-service 115MiB 115MiB +151B
authorization-service 112MiB 112MiB +322B
presence-service 112MiB 112MiB +903B

📊 Historical Trend

---
config:
  theme: "dark"
  xyChart:
    width: 900
    height: 400
---
xychart
  title "Image Size Evolution by Service (Last 30 Days + This PR)"
  x-axis ["11/19 23:02", "11/21 16:49", "11/24 17:34", "11/27 22:32", "11/28 19:05", "12/01 23:01", "12/02 21:57", "12/03 21:00", "12/04 18:17", "12/05 21:56", "12/08 20:15", "12/09 22:17", "12/10 23:26", "12/11 21:56", "12/12 22:45", "12/13 01:34", "12/15 22:31", "12/16 22:18", "12/17 21:04", "12/18 23:12", "12/19 23:27", "12/20 21:03", "12/22 18:54", "12/23 16:16", "12/24 19:38", "12/25 17:51", "12/26 13:18", "12/29 19:01", "12/30 20:52", "02/12 20:42", "02/12 22:57 (PR)"]
  y-axis "Size (GB)" 0 --> 0.5
  line "account-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11]
  line "authorization-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11]
  line "ddp-streamer-service" [0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12]
  line "omnichannel-transcript-service" [0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13]
  line "presence-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11]
  line "queue-worker-service" [0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13]
  line "rocketchat" [0.35, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.35]
Loading

Statistics (last 30 days):

  • 📊 Average: 1.5GiB
  • ⬇️ Minimum: 1.4GiB
  • ⬆️ Maximum: 1.6GiB
  • 🎯 Current PR: 1.1GiB
ℹ️ About this report

This report compares Docker image sizes from this build against the develop baseline.

  • Tag: pr-38615
  • Baseline: develop
  • Timestamp: 2026-02-12 22:57:50 UTC
  • Historical data points: 30

Updated: Thu, 12 Feb 2026 22:57:50 GMT

@KevLehman
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@codecov
Copy link

codecov bot commented Feb 11, 2026

Codecov Report

❌ Patch coverage is 61.53846% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.45%. Comparing base (ebacfb6) to head (86a751a).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #38615      +/-   ##
===========================================
- Coverage    70.50%   70.45%   -0.05%     
===========================================
  Files         3174     3174              
  Lines       111063   111077      +14     
  Branches     20040    20031       -9     
===========================================
- Hits         78300    78260      -40     
- Misses       30718    30777      +59     
+ Partials      2045     2040       -5     
Flag Coverage Δ
e2e 60.40% <ø> (-0.05%) ⬇️
e2e-api 47.79% <ø> (-0.95%) ⬇️
unit 71.45% <61.53%> (-0.02%) ⬇️

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

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

Copy link
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

🤖 Fix all issues with AI agents
In `@packages/core-services/src/types/ServiceClass.ts`:
- Around line 115-119: The try/catch is not awaiting the async callback so
rejections escape and become unhandled promise rejections; update the invocation
of settingHandler.cb in ServiceClass (the handler call currently inside the try)
to await settingHandler.cb({ clientAction, setting }) (or explicitly handle its
returned Promise with .catch) and ensure the enclosing method is async or
properly returns a Promise so the await is valid; this ensures thrown errors are
caught by the surrounding try/catch instead of leaking.
🧹 Nitpick comments (3)
packages/core-services/src/types/ServiceClass.ts (2)

60-60: Map allows only one callback per setting — is this intentional?

Using a Map<string, ...> means a second onSettingChanged call for the same settingId silently overwrites the first handler. If multiple parts of a service (or a subclass) need to react to the same setting, this will drop listeners. A Map<string, Array<...>> would avoid this, though if one-handler-per-setting is by design, a warning or error on duplicate registration would make the contract explicit.


101-104: Guard condition is tightly coupled to the Map's initial state.

registerEventListener relies on settingEvents.size !== 0 to determine if the watch.settings listener has already been added. This works today because the method is always called immediately before set, but a future refactor (e.g., removing entries) could cause size to return to 0, leading to a duplicate listener registration. A dedicated boolean flag would be more robust.

Proposed fix
+  private settingListenerRegistered = false;
+
   private registerEventListener() {
-    if (this.settingEvents.size !== 0) {
+    if (this.settingListenerRegistered) {
       return;
     }
+    this.settingListenerRegistered = true;

     this.onEvent('watch.settings', async ({ clientAction, setting }): Promise<void> => {
ee/apps/account-service/src/Account.ts (1)

20-23: Unsafe as number cast — consider a type guard like the other call-sites.

FederationMatrix guards with typeof value === 'boolean' before assignment. Here value is ISetting['value'] (a broad union) and is cast without validation. A typeof value === 'number' guard would be consistent with the pattern established elsewhere in this PR.

Proposed fix
 		this.onSettingChanged(
 			'Accounts_LoginExpiration',
 			async ({ setting }): Promise<void> => {
 				const { value } = setting;
-
-				this.loginExpiration = getLoginExpirationInDays(value as number);
+				if (typeof value === 'number') {
+					this.loginExpiration = getLoginExpirationInDays(value);
+				}
 			},
 			['removed'],
 		);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f5df55 and 9189384.

📒 Files selected for processing (6)
  • apps/meteor/ee/server/local-services/instance/service.ts
  • apps/meteor/tests/unit/server/services/instance/service.tests.ts
  • ee/apps/account-service/src/Account.ts
  • ee/packages/federation-matrix/src/FederationMatrix.ts
  • packages/core-services/src/events/Events.ts
  • packages/core-services/src/types/ServiceClass.ts
🧰 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:

  • packages/core-services/src/events/Events.ts
  • ee/apps/account-service/src/Account.ts
  • apps/meteor/tests/unit/server/services/instance/service.tests.ts
  • ee/packages/federation-matrix/src/FederationMatrix.ts
  • packages/core-services/src/types/ServiceClass.ts
  • apps/meteor/ee/server/local-services/instance/service.ts
🧠 Learnings (9)
📓 Common learnings
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37357
File: apps/meteor/ee/server/startup/federation.ts:39-74
Timestamp: 2025-11-05T20:53:57.761Z
Learning: In Rocket.Chat (apps/meteor/app/settings/server/CachedSettings.ts), the settings.watchMultiple() method immediately invokes its callback with current values if all requested settings exist in the store, then continues watching for subsequent changes. It does not wait for a setting to change before the first invocation.
📚 Learning: 2026-01-27T20:57:56.529Z
Learnt from: nazabucciarelli
Repo: RocketChat/Rocket.Chat PR: 38294
File: apps/meteor/server/hooks/sauMonitorHooks.ts:0-0
Timestamp: 2026-01-27T20:57:56.529Z
Learning: In Rocket.Chat, the `accounts.login` event listened to by DeviceManagementService is only broadcast when running in microservices mode (via DDPStreamer), not in monolith mode. The `Accounts.onLogin` hook in sauMonitorHooks.ts runs in monolith deployments. These are mutually exclusive deployment modes, so there's no duplication of event emissions between these two code paths.

Applied to files:

  • ee/apps/account-service/src/Account.ts
  • apps/meteor/ee/server/local-services/instance/service.ts
📚 Learning: 2025-11-05T20:53:57.761Z
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37357
File: apps/meteor/ee/server/startup/federation.ts:39-74
Timestamp: 2025-11-05T20:53:57.761Z
Learning: In Rocket.Chat (apps/meteor/app/settings/server/CachedSettings.ts), the settings.watchMultiple() method immediately invokes its callback with current values if all requested settings exist in the store, then continues watching for subsequent changes. It does not wait for a setting to change before the first invocation.

Applied to files:

  • ee/apps/account-service/src/Account.ts
  • apps/meteor/tests/unit/server/services/instance/service.tests.ts
  • ee/packages/federation-matrix/src/FederationMatrix.ts
  • packages/core-services/src/types/ServiceClass.ts
  • apps/meteor/ee/server/local-services/instance/service.ts
📚 Learning: 2025-10-06T20:30:45.540Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 37152
File: packages/apps-engine/tests/test-data/storage/storage.ts:101-122
Timestamp: 2025-10-06T20:30:45.540Z
Learning: In `packages/apps-engine/tests/test-data/storage/storage.ts`, the stub methods (updatePartialAndReturnDocument, updateStatus, updateSetting, updateAppInfo, updateMarketplaceInfo) intentionally throw "Method not implemented." Tests using these methods must stub them using `SpyOn` from the test library rather than relying on actual implementations.

Applied to files:

  • apps/meteor/tests/unit/server/services/instance/service.tests.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/tests/unit/server/services/instance/service.tests.ts
📚 Learning: 2025-09-19T15:15:04.642Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat PR: 36991
File: apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Settings.ts:219-221
Timestamp: 2025-09-19T15:15:04.642Z
Learning: The Federation_Matrix_homeserver_domain setting in apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Settings.ts is part of the old federation system and is being deprecated/removed, so configuration issues with this setting should not be flagged for improvement.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
  • apps/meteor/ee/server/local-services/instance/service.ts
📚 Learning: 2025-11-05T21:04:35.787Z
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37357
File: ee/packages/federation-matrix/src/setup.ts:103-120
Timestamp: 2025-11-05T21:04:35.787Z
Learning: In Rocket.Chat's federation-matrix setup (ee/packages/federation-matrix/src/setup.ts and apps/meteor/ee/server/startup/federation.ts), configureFederationMatrixSettings does not need to be called before setupFederationMatrix. The SDK's init() establishes infrastructure (database, event handlers, APIs) first, and the configuration can be applied later via settings watchers before actual federation events are processed. The config only matters when events actually occur, at which point all infrastructure is already configured.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
📚 Learning: 2025-10-28T16:53:42.761Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 37205
File: ee/packages/federation-matrix/src/FederationMatrix.ts:296-301
Timestamp: 2025-10-28T16:53:42.761Z
Learning: In the Rocket.Chat federation-matrix integration (ee/packages/federation-matrix/), the createRoom method from rocket.chat/federation-sdk will support a 4-argument signature (userId, roomName, visibility, displayName) in newer versions. Code using this 4-argument call is forward-compatible with planned library updates and should not be flagged as an error.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
📚 Learning: 2025-12-09T20:01:00.324Z
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37532
File: ee/packages/federation-matrix/src/FederationMatrix.ts:920-927
Timestamp: 2025-12-09T20:01:00.324Z
Learning: When reviewing federation invite handling in Rocket.Chat (specifically under ee/packages/federation-matrix), understand that rejecting an invite via federationSDK.rejectInvite() triggers an event-driven cleanup: a leave event is emitted and handled by handleLeave() in ee/packages/federation-matrix/src/events/member.ts, which calls Room.performUserRemoval() to remove the subscription. Do not add explicit cleanup in the reject branch of handleInvite(); rely on the existing leave-event flow for cleanup. If making changes, ensure this invariant remains and that any related paths still funnel cleanup through the leave event to avoid duplicate or missing removals.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
🧬 Code graph analysis (1)
packages/core-services/src/types/ServiceClass.ts (1)
packages/core-services/src/events/Events.ts (2)
  • EventSignatures (56-309)
  • ClientAction (41-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: 🚢 Build Docker (amd64, account-service, presence-service, omnichannel-transcript-service, cove...
  • GitHub Check: 🚢 Build Docker (arm64, rocketchat, coverage)
  • GitHub Check: 🚢 Build Docker (amd64, authorization-service, queue-worker-service, ddp-streamer-service, cove...
  • GitHub Check: 🚢 Build Docker (arm64, authorization-service, queue-worker-service, ddp-streamer-service, cove...
  • GitHub Check: 🚢 Build Docker (arm64, account-service, presence-service, omnichannel-transcript-service, cove...
  • GitHub Check: 🚢 Build Docker (amd64, rocketchat, coverage)
🔇 Additional comments (4)
packages/core-services/src/events/Events.ts (1)

41-41: LGTM!

Exporting ClientAction is the right call to make it available for the new onSettingChanged API.

ee/packages/federation-matrix/src/FederationMatrix.ts (1)

44-86: Clean migration to per-setting listeners.

The pattern is consistent across all four settings: type-guarded callbacks, ['removed'] in ignoreActions, and separate initial-value fetches at lines 128–131. The register-then-fetch ordering avoids a race between initialization and event delivery.

apps/meteor/ee/server/local-services/instance/service.ts (1)

53-69: Clean migration with proper guards.

Good use of both type validation (typeof value !== 'boolean') and value-unchanged check to avoid unnecessary state updates.

apps/meteor/tests/unit/server/services/instance/service.tests.ts (1)

18-22: Mock updated correctly.

The stub enables the constructor to execute without errors. Consider adding an assertion that onSettingChanged was called with 'Troubleshoot_Disable_Instance_Broadcast' and ['removed'] to verify the registration wiring.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@KevLehman KevLehman marked this pull request as ready for review February 11, 2026 18:50
@KevLehman KevLehman requested a review from a team as a code owner February 11, 2026 18:50
Copy link
Contributor

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

Choose a reason for hiding this comment

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

No issues found across 6 files

Copy link
Contributor

@ricardogarim ricardogarim left a comment

Choose a reason for hiding this comment

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

For brainstorming: have you considered emitting granular events like settings.{SETTING_NAME} at the source instead of filtering at the service level? This way services could subscribe directly to the settings they care about without needing the wrapper.

Also, would worth documenting this pattern in an ADR for future reference.

@KevLehman
Copy link
Member Author

For brainstorming: have you considered emitting granular events like settings.{SETTING_NAME} at the source instead of filtering at the service level? This way services could subscribe directly to the settings they care about without needing the wrapper.

This was one of the ideas, but it would generate a bit of noise, for example, when registering listeners if you register 100 settings to watch, then it would mean you will have 100 event listeners attached, and those events are going into moleculer events (as we use it for knowing which events to propagate to where). So we discarded it.

@sampaiodiego
Copy link
Member

sampaiodiego commented Feb 12, 2026

For brainstorming: have you considered emitting granular events like settings.{SETTING_NAME} at the source instead of filtering at the service level? This way services could subscribe directly to the settings they care about without needing the wrapper.

This was one of the ideas, but it would generate a bit of noise, for example, when registering listeners if you register 100 settings to watch, then it would mean you will have 100 event listeners attached, and those events are going into moleculer events (as we use it for knowing which events to propagate to where). So we discarded it.

additionally to this, we know that settings don't change very often, so having a single event to receive all settings changes is not that costly.

Copy link
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.

Caution

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

⚠️ Outside diff range comments (1)
packages/core-services/src/types/ServiceClass.ts (1)

83-85: ⚠️ Potential issue | 🟠 Major

removeAllListeners doesn't clear settings emitter or reset the guard flag.

After removeAllListeners() runs, the watch.settings handler on this.events is gone, but settingListenerActive stays true. Any subsequent call to onSettingChanged skips re-registration, so the settings emitter's listeners become orphaned — they never fire and they leak.

Proposed fix
 	removeAllListeners(): void {
 		this.events.removeAllListeners();
+		this.settings.removeAllListeners();
+		this.settingListenerActive = false;
 	}
🧹 Nitpick comments (2)
packages/core-services/src/types/ServiceClass.ts (2)

60-60: Consider increasing maxListeners on the settings emitter.

Node.js EventEmitter defaults to 10 listeners before emitting a MaxListenersExceededWarning. A service watching more than 10 distinct settings (each registers a listener) will trigger this warning. Since each setting ID gets its own event name this is unlikely to hit per-event, but if a single setting is watched multiple times (e.g., across mixins or re-registrations), it could surface.

Proposed fix
-	protected settings = new EventEmitter();
+	protected settings = new EventEmitter({ captureRejections: true });
+
+	constructor() {
+		super();
+		this.settings.setMaxListeners(0); // unlimited — one listener per watched setting
+		this.emit = this.emit.bind(this);
+	}

Note: since ServiceClass doesn't extend another class, drop the super() call — just set it in the existing constructor:

 	constructor() {
 		this.emit = this.emit.bind(this);
+		this.settings.setMaxListeners(0);
 	}

Also applies to: 128-138


133-137: Silent error swallowing hinders debugging.

The catch { // noop } block discards all errors from setting-change callbacks. At minimum, a debug/warn-level log would help operators diagnose misbehaving handlers without changing the "don't crash" intent.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 933fc14 and 81e0e91.

📒 Files selected for processing (1)
  • packages/core-services/src/types/ServiceClass.ts
🧰 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:

  • packages/core-services/src/types/ServiceClass.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37357
File: apps/meteor/ee/server/startup/federation.ts:39-74
Timestamp: 2025-11-05T20:53:57.761Z
Learning: In Rocket.Chat (apps/meteor/app/settings/server/CachedSettings.ts), the settings.watchMultiple() method immediately invokes its callback with current values if all requested settings exist in the store, then continues watching for subsequent changes. It does not wait for a setting to change before the first invocation.
📚 Learning: 2025-09-19T15:15:04.642Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat PR: 36991
File: apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Settings.ts:219-221
Timestamp: 2025-09-19T15:15:04.642Z
Learning: The Federation_Matrix_homeserver_domain setting in apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Settings.ts is part of the old federation system and is being deprecated/removed, so configuration issues with this setting should not be flagged for improvement.

Applied to files:

  • packages/core-services/src/types/ServiceClass.ts
📚 Learning: 2025-11-05T20:53:57.761Z
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37357
File: apps/meteor/ee/server/startup/federation.ts:39-74
Timestamp: 2025-11-05T20:53:57.761Z
Learning: In Rocket.Chat (apps/meteor/app/settings/server/CachedSettings.ts), the settings.watchMultiple() method immediately invokes its callback with current values if all requested settings exist in the store, then continues watching for subsequent changes. It does not wait for a setting to change before the first invocation.

Applied to files:

  • packages/core-services/src/types/ServiceClass.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (2)
packages/core-services/src/types/ServiceClass.ts (2)

42-46: Interface addition looks good.

The onSettingChanged signature is well-typed with proper use of ISetting['_id'], the async callback, and the optional ignoreActions filter.


107-119: Registration logic is clean.

The one-time guard and re-emission by _id are straightforward. The prior review concern about missing await on the callback has been addressed in the current code (line 134).

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/core-services/src/types/ServiceClass.ts">

<violation number="1" location="packages/core-services/src/types/ServiceClass.ts:60">
P1: `removeAllListeners()` does not clean up the new `settings` EventEmitter or reset `settingListenerActive`. After `removeAllListeners()` is called (e.g., during service teardown via `LocalBroker`), the `watch.settings` handler is removed from `this.events`, but `settingListenerActive` remains `true`. Any subsequent call to `onSettingChanged()` will skip re-registering the bridge listener, silently breaking setting change delivery. Additionally, the `settings` EventEmitter retains stale listeners.

Update `removeAllListeners()` to also reset the settings state.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
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

🤖 Fix all issues with AI agents
In `@packages/core-services/src/types/ServiceClass.ts`:
- Around line 83-86: removeAllListeners() currently clears this.events and
this.settings but leaves the settingListenerActive flag true, which causes
registerEventListener() to early-return and prevents re-establishing the
watch.settings → per-setting bridge when onSettingChanged() runs; update
removeAllListeners() to also set this.settingListenerActive = false so
subsequent onSettingChanged() calls can re-register per-setting listeners via
registerEventListener().
🧹 Nitpick comments (2)
packages/core-services/src/types/ServiceClass.ts (2)

108-120: Async listeners on EventEmitter.emit are fire-and-forget — consider documenting or mitigating.

this.settings.emit(…) on line 118 invokes async listeners synchronously; their returned promises are discarded. In this design the inner try/catch (line 134-138) guards against rejections, so it works today, but any future listener that throws before the try-block (or if the catch itself throws) will produce an unhandled promise rejection. A small mitigation is to add a safety .catch on the emit side or use an async-safe emitter.

This is acceptable for now given the narrow scope, but worth keeping in mind.


134-138: Silent catch swallows all errors — consider at least logging.

Silently catching every error (// noop) makes it very hard to diagnose why a setting-change handler isn't taking effect. A debug/warn-level log with the setting id would be low-cost and high-value.

Also, per coding guidelines, the // noop comment should be avoided. As per coding guidelines: "Avoid code comments in the implementation."

Proposed improvement
 			try {
 				await cb({ clientAction, setting });
-			} catch {
-				// noop
+			} catch (err) {
+				console.error(`onSettingChanged handler for "${settingId}" failed`, err);
 			}

(Or use the service's logger if available.)

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a632eb and c4c715f.

📒 Files selected for processing (4)
  • apps/meteor/ee/server/local-services/instance/service.ts
  • ee/apps/account-service/src/Account.ts
  • ee/packages/federation-matrix/src/FederationMatrix.ts
  • packages/core-services/src/types/ServiceClass.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/meteor/ee/server/local-services/instance/service.ts
🧰 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:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
  • packages/core-services/src/types/ServiceClass.ts
  • ee/apps/account-service/src/Account.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37357
File: apps/meteor/ee/server/startup/federation.ts:39-74
Timestamp: 2025-11-05T20:53:57.761Z
Learning: In Rocket.Chat (apps/meteor/app/settings/server/CachedSettings.ts), the settings.watchMultiple() method immediately invokes its callback with current values if all requested settings exist in the store, then continues watching for subsequent changes. It does not wait for a setting to change before the first invocation.
📚 Learning: 2025-09-19T15:15:04.642Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat PR: 36991
File: apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Settings.ts:219-221
Timestamp: 2025-09-19T15:15:04.642Z
Learning: The Federation_Matrix_homeserver_domain setting in apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Settings.ts is part of the old federation system and is being deprecated/removed, so configuration issues with this setting should not be flagged for improvement.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
  • packages/core-services/src/types/ServiceClass.ts
📚 Learning: 2025-11-05T21:04:35.787Z
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37357
File: ee/packages/federation-matrix/src/setup.ts:103-120
Timestamp: 2025-11-05T21:04:35.787Z
Learning: In Rocket.Chat's federation-matrix setup (ee/packages/federation-matrix/src/setup.ts and apps/meteor/ee/server/startup/federation.ts), configureFederationMatrixSettings does not need to be called before setupFederationMatrix. The SDK's init() establishes infrastructure (database, event handlers, APIs) first, and the configuration can be applied later via settings watchers before actual federation events are processed. The config only matters when events actually occur, at which point all infrastructure is already configured.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
📚 Learning: 2025-11-05T20:53:57.761Z
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37357
File: apps/meteor/ee/server/startup/federation.ts:39-74
Timestamp: 2025-11-05T20:53:57.761Z
Learning: In Rocket.Chat (apps/meteor/app/settings/server/CachedSettings.ts), the settings.watchMultiple() method immediately invokes its callback with current values if all requested settings exist in the store, then continues watching for subsequent changes. It does not wait for a setting to change before the first invocation.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
  • packages/core-services/src/types/ServiceClass.ts
  • ee/apps/account-service/src/Account.ts
📚 Learning: 2025-12-09T20:01:00.324Z
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37532
File: ee/packages/federation-matrix/src/FederationMatrix.ts:920-927
Timestamp: 2025-12-09T20:01:00.324Z
Learning: When reviewing federation invite handling in Rocket.Chat (specifically under ee/packages/federation-matrix), understand that rejecting an invite via federationSDK.rejectInvite() triggers an event-driven cleanup: a leave event is emitted and handled by handleLeave() in ee/packages/federation-matrix/src/events/member.ts, which calls Room.performUserRemoval() to remove the subscription. Do not add explicit cleanup in the reject branch of handleInvite(); rely on the existing leave-event flow for cleanup. If making changes, ensure this invariant remains and that any related paths still funnel cleanup through the leave event to avoid duplicate or missing removals.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
🧬 Code graph analysis (1)
packages/core-services/src/types/ServiceClass.ts (1)
packages/core-services/src/events/Events.ts (1)
  • ClientAction (41-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (3)
packages/core-services/src/types/ServiceClass.ts (1)

42-46: LGTM — clean API surface for the new hook.

The onSettingChanged interface declaration is well-typed and concise. Defaulting ignoreActions to optional keeps the call sites clean.

ee/apps/account-service/src/Account.ts (1)

18-22: Clean migration to onSettingChanged — initial value correctly loaded in started().

The handler only fires on subsequent changes, while started() (line 41-44) covers the initial load from the database. This avoids the footgun of assuming the hook fires immediately (unlike the old settings.watchMultiple() which did). The default ignoreActions: ['removed'] is appropriate here.

ee/packages/federation-matrix/src/FederationMatrix.ts (1)

44-70: Clean decomposition — individual handlers are easier to read and maintain than the prior combined listener.

Each handler correctly type-guards value before assignment and relies on the default ignoreActions: ['removed']. Initial state is properly bootstrapped from the DB at lines 112-115.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@KevLehman KevLehman added this to the 8.2.0 milestone Feb 12, 2026
@KevLehman KevLehman added the stat: QA assured Means it has been tested and approved by a company insider label Feb 12, 2026
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Feb 12, 2026
@KevLehman KevLehman dismissed ricardogarim’s stale review February 13, 2026 02:55

applied ur suggestions :p

@kodiakhq kodiakhq bot merged commit 51c3c76 into develop Feb 13, 2026
46 checks passed
@kodiakhq kodiakhq bot deleted the chore/new-setting-hook-svr branch February 13, 2026 02:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants