ppy/osu · warning · InvalidOperationException
Cannot toggle spectate when in {localUser.State}
Error message
Cannot toggle spectate when in {localUser.State} What it means
Thrown by MultiplayerClient.ToggleSpectate when the local user's state is not Idle, Ready, or Spectating. The toggle only flips between spectating and idle from those entry points; mid-match states are rejected.
Source
Thrown at osu.Game/Online/Multiplayer/MultiplayerClient.cs:474
{
var localUser = LocalUser;
if (localUser == null)
return;
switch (localUser.State)
{
case MultiplayerUserState.Idle:
case MultiplayerUserState.Ready:
await ChangeState(MultiplayerUserState.Spectating).ConfigureAwait(false);
return;
case MultiplayerUserState.Spectating:
await ChangeState(MultiplayerUserState.Idle).ConfigureAwait(false);
return;
default:
throw new InvalidOperationException($"Cannot toggle spectate when in {localUser.State}");
}
}
public abstract Task TransferHost(int userId);
public abstract Task KickUser(int userId);
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
public abstract Task ChangeState(MultiplayerUserState newState);
public abstract Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability);
public abstract Task ChangeUserStyle(int? beatmapId, int? rulesetId);
/// <summary>
/// Change the local user's mods in the currently joined room.
/// </summary>
View on GitHub (pinned to d9c73e12ad)
Solutions
- Gate the spectate toggle on localUser.State being Idle, Ready, or Spectating.
- Disable the spectate button while in Loaded/Playing/Results.
- If encountered, defer the toggle until the match ends and state returns to Idle.
Example fix
// before
spectateButton.Action = () => client.ToggleSpectate();
// after
spectateButton.Action = () =>
{
var s = client.LocalUser?.State;
if (s == MultiplayerUserState.Idle || s == MultiplayerUserState.Ready || s == MultiplayerUserState.Spectating)
client.ToggleSpectate();
}; Defensive patterns
Strategy: validation
Validate before calling
var s = client.LocalUser?.State;
if (s == MultiplayerUserState.Idle || s == MultiplayerUserState.Ready || s == MultiplayerUserState.Spectating)
await client.ToggleSpectate(); Type guard
bool CanToggleSpectate(MultiplayerUserState? s)
=> s == MultiplayerUserState.Idle || s == MultiplayerUserState.Ready || s == MultiplayerUserState.Spectating; Try / catch
try { await client.ToggleSpectate(); }
catch (InvalidOperationException) { /* invalid state: ignore the click */ } Prevention
- Disable the spectate button during Loaded/Playing/Results.
- Subscribe to LocalUser state changes to refresh button state.
- Avoid toggling spectate mid-match.
When it happens
Trigger: Calling ToggleSpectate() while localUser.State is Loaded, Playing, or Results.
Common situations: Spectate toggle clicked during an active match; UI not disabled while the user is playing.
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 join an inactive room.
AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13).
Data as JSON: /api/errors/0918219871c022c3.
Report an issue: GitHub.