ppy/osu · error · InvalidOperationException
Cannot join an inactive room.
Error message
Cannot join an inactive room.
What it means
Thrown by MultiplayerClient.JoinRoom when the passed Room has no RoomID. A room must exist server-side (have been created) to be joinable; a locally constructed Room with null RoomID cannot be joined.
Source
Thrown at osu.Game/Online/Multiplayer/MultiplayerClient.cs:264
await runOnUpdateThreadAsync(() => pendingRequests.Clear(), cancellationSource.Token).ConfigureAwait(false);
var multiplayerRoom = await CreateRoomInternal(new MultiplayerRoom(room)).ConfigureAwait(false);
await setupJoinedRoom(room, multiplayerRoom, cancellationSource.Token).ConfigureAwait(false);
}, cancellationSource.Token).ConfigureAwait(false);
}
/// <summary>
/// Joins the <see cref="MultiplayerRoom"/> for a given API <see cref="Room"/>.
/// </summary>
/// <param name="room">The API <see cref="Room"/>.</param>
/// <param name="password">An optional password to use for the join operation.</param>
/// <exception cref="InvalidOperationException">If the current user is already in another room, or <paramref name="room"/> does not represent an active room.</exception>
public async Task JoinRoom(Room room, string? password = null)
{
if (Room != null)
throw new InvalidOperationException("Cannot join a multiplayer room while already in one.");
if (room.RoomID == null)
throw new InvalidOperationException("Cannot join an inactive room.");
var cancellationSource = joinCancellationSource = new CancellationTokenSource();
await joinOrLeaveTaskChain.Add(async () =>
{
await runOnUpdateThreadAsync(() => pendingRequests.Clear(), cancellationSource.Token).ConfigureAwait(false);
var multiplayerRoom = await JoinRoomInternal(room.RoomID.Value, password ?? room.Password).ConfigureAwait(false);
await setupJoinedRoom(room, multiplayerRoom, cancellationSource.Token).ConfigureAwait(false);
}, cancellationSource.Token).ConfigureAwait(false);
}
/// <summary>
/// Performs post-join setup of a <see cref="MultiplayerRoom"/>.
/// </summary>
/// <param name="apiRoom">The incoming API <see cref="Room"/> that was requested to be joined.</param>
/// <param name="joinedRoom">The resuling <see cref="MultiplayerRoom"/> that was joined.</param>
/// <param name="cancellationToken">A token to cancel the process.</param>
private async Task setupJoinedRoom(Room apiRoom, MultiplayerRoom joinedRoom, CancellationToken cancellationToken)
View on GitHub (pinned to d9c73e12ad)
Solutions
- Only join rooms obtained from the API listing (CreateRoom result or a fetched Room) so RoomID is populated.
- Guard: if (room.RoomID == null) reject the join in the UI.
- Verify the API response deserialized RoomID correctly (check JSON property mapping).
Example fix
// before
await client.JoinRoom(new Room { Name = "test" });
// after
if (room.RoomID == null) throw new InvalidOperationException("room not created server-side");
await client.JoinRoom(room); Defensive patterns
Strategy: validation
Validate before calling
if (room.RoomID == null)
throw new InvalidOperationException("Cannot join: room has no RoomID.");
await client.JoinRoom(room, password); Type guard
bool IsJoinable(Room room) => room.RoomID.HasValue;
Try / catch
try { await client.JoinRoom(room, password); }
catch (InvalidOperationException ex) { /* inactive room: require a created/listed Room */ } Prevention
- Only join Room instances returned from CreateRoom or the API listing.
- Verify RoomID survived JSON deserialization.
- Reject null-RoomID rooms in the UI before invoking join.
When it happens
Trigger: Calling JoinRoom(room) where room.RoomID == null, i.e. using a Room instance that was never created via CreateRoom or fetched from the API room listings.
Common situations: Passing a freshly `new Room { Name = ... }` to JoinRoom instead of a Room object returned from the API listing endpoint; deserialization dropping the RoomID field.
Related errors
- Cannot create a multiplayer room while already in one.
- Cannot join a multiplayer room while already in one.
- Must be joined to a match to change settings.
- Cannot toggle ready when in {localUser.State}
- Cannot toggle spectate when in {localUser.State}
AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13).
Data as JSON: /api/errors/f3504286406357ea.
Report an issue: GitHub.