Skip to content

fix: use actual room versions instead of defaultRoomVersion everywhere - #407

Open
debdutdeb wants to merge 1 commit into
mainfrom
fix-use-actual-room-versions
Open

fix: use actual room versions instead of defaultRoomVersion everywhere#407
debdutdeb wants to merge 1 commit into
mainfrom
fix-use-actual-room-versions

Conversation

@debdutdeb

@debdutdeb debdutdeb commented Aug 5, 2026

Copy link
Copy Markdown
Member

defaultRoomVersion should only be used at creation of a room. Everywhere else, grab version from state or from create event whichever is closer.

From claude

Function names using defaultRoomVersion (non-test files):

newCreateEvent (packages/room/src/manager/factory.ts)
getRoomVersion (packages/federation-sdk/src/services/event.service.ts)
createRoomV2 (packages/federation-sdk/src/services/room.service.ts)
createDirectMessageRoom (packages/federation-sdk/src/services/room.service.ts)

getRoomVersion returns default if fails to get from state, i'd argue we allow it to return undefined instead. but not touching that code here.

Claude wrote this patch.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added support for retrieving a room’s negotiated room version through the federation SDK.
    • Federation requests can now automatically determine the target room version when it isn’t specified.
  • Bug Fixes

    • Room events now consistently use each room’s resolved version, improving federation joins, power-level updates, and room retirements.
    • Improved handling and logging when rejoining rooms with existing local state.
  • Changes

    • New rooms now default to room version 11.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

Room version federation flow

Layer / File(s) Summary
Room version sources and defaults
packages/federation-sdk/src/sdk.ts, packages/room/src/manager/factory.ts
The SDK exposes getRoomVersion. The persistent event factory default changes from room version 10 to 11.
Version-aware room operations
packages/federation-sdk/src/services/room.service.ts
Power-level, join, rejoin, and tombstone events use the resolved room version. Remote joins distinguish existing local rooms from first-time joins.
External request version resolution
packages/homeserver/src/controllers/internal/external-federation-request.controller.ts
Event requests fetch the target room version when no version is supplied. The send endpoint version query is optional.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RoomService
  participant StateService
  participant Federation
  RoomService->>StateService: Resolve existing room version
  StateService-->>RoomService: Return room version or UnknownRoomError
  RoomService->>Federation: Request join with known room version
  Federation-->>RoomService: Return join state
  RoomService->>RoomService: Process join state using rejoin status
Loading

Possibly related PRs

Suggested labels: type: bug

Suggested reviewers: sampaiodiego

🚥 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 clearly summarizes the main change: using resolved room versions instead of defaultRoomVersion across federation and room operations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.38462% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.79%. Comparing base (d387798) to head (ac78100).

Files with missing lines Patch % Lines
...ckages/federation-sdk/src/services/room.service.ts 65.21% 8 Missing ⚠️
packages/federation-sdk/src/sdk.ts 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #407      +/-   ##
==========================================
- Coverage   51.81%   51.79%   -0.03%     
==========================================
  Files         114      114              
  Lines       12704    12712       +8     
==========================================
+ Hits         6583     6584       +1     
- Misses       6121     6128       +7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/federation-sdk/src/services/room.service.ts`:
- Around line 765-781: After makeJoinResponse is returned in the rejoin path,
compare its room_version with knownRoomVersion before calling sendJoin; reject
or throw on a mismatch, while preserving the existing flow for new joins where
knownRoomVersion is undefined. Locate the sendJoin invocation and enforce this
validation before it.
🪄 Autofix

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 Plus

Run ID: fe4a6e52-5f75-4d44-b991-fab96b789a5f

📥 Commits

Reviewing files that changed from the base of the PR and between d387798 and ac78100.

📒 Files selected for processing (4)
  • packages/federation-sdk/src/sdk.ts
  • packages/federation-sdk/src/services/room.service.ts
  • packages/homeserver/src/controllers/internal/external-federation-request.controller.ts
  • packages/room/src/manager/factory.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Code Quality Checks(lint, test, tsc)
🔇 Additional comments (8)
packages/federation-sdk/src/sdk.ts (1)

262-264: LGTM!

packages/room/src/manager/factory.ts (1)

41-41: LGTM!

packages/federation-sdk/src/services/room.service.ts (4)

443-443: LGTM!

Also applies to: 517-517


746-746: LGTM!


811-813: LGTM!


1131-1131: LGTM!

Also applies to: 1182-1182

packages/homeserver/src/controllers/internal/external-federation-request.controller.ts (2)

64-64: LGTM!


206-206: LGTM!

Also applies to: 222-222

Comment on lines +765 to +781
// Resident server is remote, need to do join flow.
// If we already have local state for this room (re-join), hint our known
// version to the resident server instead of asking it to pick from the
// full supported list.
let knownRoomVersion: RoomVersion | undefined;
let isRejoin = false;
try {
knownRoomVersion = await stateService.getRoomVersion(roomId);
isRejoin = true;
} catch (error) {
if (!(error instanceof UnknownRoomError)) {
throw error;
}
}

// trying to join room from another server
const makeJoinResponse = await federationService.makeJoin(
residentServer,
roomId,
userId,
roomVersion, // NOTE: check the comment in the called method
);
const makeJoinResponse = await federationService.makeJoin(residentServer, roomId, userId, knownRoomVersion);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the makeJoin implementation and its tests.
rg -n -C 8 --type ts '\bmakeJoin\s*\(' packages/federation-sdk/src

# Inspect whether the implementation compares a requested known version with
# the returned room_version before returning the response to RoomService.
fd -t f --extension ts . packages/federation-sdk/src | while IFS= read -r file; do
  rg -n -C 12 'knownRoomVersion|room_version|makeJoin' "$file" || true
done

Repository: RocketChat/homeserver

Length of output: 46182


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== FederationService.makeJoin implementation =="
sed -n '1,55p' packages/federation-sdk/src/services/federation.service.ts

echo
echo "== RoomService.joinRoom relevant section =="
sed -n '760,905p' packages/federation-sdk/src/services/room.service.ts

echo
echo "== sendJoin implementation =="
sed -n '44,70p' packages/federation-sdk/src/services/federation.service.ts

echo
echo "== Schemas =="
sed -n '87,102p' packages/federation-sdk/src/services/federation.service.ts 2>/dev/null || rg -n -C 8 'MakeJoinResponse|MakeJoinEvent' packages/federation-sdk/src | head -80

echo
echo "== Search for makeJoinResponse.room_version validation =="
rg -n -C 5 'makeJoinResponse\.room_version|if \(.*room_version|throw.*room_version|not equal|!==|!=|same' packages/federation-sdk/src/services/room.service.ts packages/federation-sdk/src/services/federation.service.ts packages/federation-sdk/src

Repository: RocketChat/homeserver

Length of output: 50378


Reject mismatched room versions after makeJoin.

When knownRoomVersion is set, compare makeJoinResponse.room_version to the local room version before calling sendJoin. makeJoin only passes ver to the request, so a different remote response can still be parsed with the remote version instead of the stored local room state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/federation-sdk/src/services/room.service.ts` around lines 765 - 781,
After makeJoinResponse is returned in the rejoin path, compare its room_version
with knownRoomVersion before calling sendJoin; reject or throw on a mismatch,
while preserving the existing flow for new joins where knownRoomVersion is
undefined. Locate the sendJoin invocation and enforce this validation before it.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/homeserver/src/controllers/internal/external-federation-request.controller.ts">

<violation number="1" location="packages/homeserver/src/controllers/internal/external-federation-request.controller.ts:64">
P2: The `/internal/event/template` devtools endpoint used to fall back to `PersistentEventFactory.defaultRoomVersion` when the client didn't pass `?version=`. Now `getRoomVersion(roomId)` is the fallback, but `StateService.getRoomVersion` throws `UnknownRoomError` when the room has no local `m.room.create` event. This endpoint generates a template event to fill in and send, frequently for rooms you haven't joined locally or are about to create, so the unhandled throw now turns the endpoint into a 500 instead of returning a template with a default version. Consider catching `UnknownRoomError` (or a `.catch` fallback) so the page keeps working for rooms without local state.</violation>

<violation number="2" location="packages/homeserver/src/controllers/internal/external-federation-request.controller.ts:206">
P2: Same regression as the template endpoint: the `/internal/event/send` route used to default to `PersistentEventFactory.defaultRoomVersion`, but the new fallback calls `federationSDK.getRoomVersion(event.room_id)`, which throws `UnknownRoomError` when the target room has no local `m.room.create` event. The query schema was also changed so `version` is now fully optional. This makes it impossible to send a room's first events (e.g. the initial `m.room.create`/`m.room.member` events for a brand-new room) through this devtool, because the version lookup itself raises before the event is built. Add a `UnknownRoomError` guard or a `.catch` fallback to `defaultRoomVersion` so version resolution failures degrade gracefully.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

async ({ body, query }) => {
const event = body as Pdu;
const version = (query.version as RoomVersion | undefined) || PersistentEventFactory.defaultRoomVersion;
const version = (query?.version as RoomVersion | undefined) || (await federationSDK.getRoomVersion(event.room_id));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Same regression as the template endpoint: the /internal/event/send route used to default to PersistentEventFactory.defaultRoomVersion, but the new fallback calls federationSDK.getRoomVersion(event.room_id), which throws UnknownRoomError when the target room has no local m.room.create event. The query schema was also changed so version is now fully optional. This makes it impossible to send a room's first events (e.g. the initial m.room.create/m.room.member events for a brand-new room) through this devtool, because the version lookup itself raises before the event is built. Add a UnknownRoomError guard or a .catch fallback to defaultRoomVersion so version resolution failures degrade gracefully.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/homeserver/src/controllers/internal/external-federation-request.controller.ts, line 206:

<comment>Same regression as the template endpoint: the `/internal/event/send` route used to default to `PersistentEventFactory.defaultRoomVersion`, but the new fallback calls `federationSDK.getRoomVersion(event.room_id)`, which throws `UnknownRoomError` when the target room has no local `m.room.create` event. The query schema was also changed so `version` is now fully optional. This makes it impossible to send a room's first events (e.g. the initial `m.room.create`/`m.room.member` events for a brand-new room) through this devtool, because the version lookup itself raises before the event is built. Add a `UnknownRoomError` guard or a `.catch` fallback to `defaultRoomVersion` so version resolution failures degrade gracefully.</comment>

<file context>
@@ -203,7 +203,7 @@ export const internalRequestPlugin = (app: Elysia) => {
 		async ({ body, query }) => {
 			const event = body as Pdu;
-			const version = (query.version as RoomVersion | undefined) || PersistentEventFactory.defaultRoomVersion;
+			const version = (query?.version as RoomVersion | undefined) || (await federationSDK.getRoomVersion(event.room_id));
 			if (!PersistentEventFactory.isSupportedRoomVersion(version)) {
 				throw new Error(`Room version ${version} is not supported`);
</file context>

sender: UserID;
};
const version = (query.version as RoomVersion | undefined) || PersistentEventFactory.defaultRoomVersion;
const version = (query.version as RoomVersion | undefined) || (await federationSDK.getRoomVersion(roomId));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The /internal/event/template devtools endpoint used to fall back to PersistentEventFactory.defaultRoomVersion when the client didn't pass ?version=. Now getRoomVersion(roomId) is the fallback, but StateService.getRoomVersion throws UnknownRoomError when the room has no local m.room.create event. This endpoint generates a template event to fill in and send, frequently for rooms you haven't joined locally or are about to create, so the unhandled throw now turns the endpoint into a 500 instead of returning a template with a default version. Consider catching UnknownRoomError (or a .catch fallback) so the page keeps working for rooms without local state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/homeserver/src/controllers/internal/external-federation-request.controller.ts, line 64:

<comment>The `/internal/event/template` devtools endpoint used to fall back to `PersistentEventFactory.defaultRoomVersion` when the client didn't pass `?version=`. Now `getRoomVersion(roomId)` is the fallback, but `StateService.getRoomVersion` throws `UnknownRoomError` when the room has no local `m.room.create` event. This endpoint generates a template event to fill in and send, frequently for rooms you haven't joined locally or are about to create, so the unhandled throw now turns the endpoint into a 500 instead of returning a template with a default version. Consider catching `UnknownRoomError` (or a `.catch` fallback) so the page keeps working for rooms without local state.</comment>

<file context>
@@ -61,7 +61,7 @@ export const internalRequestPlugin = (app: Elysia) => {
 				sender: UserID;
 			};
-			const version = (query.version as RoomVersion | undefined) || PersistentEventFactory.defaultRoomVersion;
+			const version = (query.version as RoomVersion | undefined) || (await federationSDK.getRoomVersion(roomId));
 			switch (eventType) {
 				case 'm.room.member': {
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants