ppy/osu · warning · InvalidOperationException
Cannot toggle ready when in {localUser.State}
Error message
Cannot toggle ready when in {localUser.State} What it means
Thrown by MultiplayerClient.ToggleReady when the local user's state is not Idle or Ready. Those are the only states from which a ready toggle is valid; any other state (Spectating, Loaded, Playing, Results) falls through to the default case.
Source
Thrown at osu.Game/Online/Multiplayer/MultiplayerClient.cs:447
public async Task ToggleReady()
{
var localUser = LocalUser;
if (localUser == null)
return;
switch (localUser.State)
{
case MultiplayerUserState.Idle:
await ChangeState(MultiplayerUserState.Ready).ConfigureAwait(false);
return;
case MultiplayerUserState.Ready:
await ChangeState(MultiplayerUserState.Idle).ConfigureAwait(false);
return;
default:
throw new InvalidOperationException($"Cannot toggle ready when in {localUser.State}");
}
}
/// <summary>
/// Toggles the <see cref="LocalUser"/>'s spectating state.
/// </summary>
/// <exception cref="InvalidOperationException">If a toggle of the spectating state is not valid at this time.</exception>
public async Task ToggleSpectate()
{
var localUser = LocalUser;
if (localUser == null)
return;
switch (localUser.State)
{
case MultiplayerUserState.Idle:
case MultiplayerUserState.Ready:
View on GitHub (pinned to d9c73e12ad)
Solutions
- Only expose the ready toggle when localUser.State is Idle or Ready.
- Bind the button's Enabled state to a check on MultiplayerClient.LocalUser?.State.
- If hit at runtime, ignore the click (the user must wait for the match to end).
Example fix
// before
readyButton.Action = () => client.ToggleReady();
// after
readyButton.Action = () =>
{
var s = client.LocalUser?.State;
if (s == MultiplayerUserState.Idle || s == MultiplayerUserState.Ready)
client.ToggleReady();
}; Defensive patterns
Strategy: validation
Validate before calling
var s = client.LocalUser?.State;
if (s == MultiplayerUserState.Idle || s == MultiplayerUserState.Ready)
await client.ToggleReady(); Type guard
bool CanToggleReady(MultiplayerUserState? s)
=> s == MultiplayerUserState.Idle || s == MultiplayerUserState.Ready; Try / catch
try { await client.ToggleReady(); }
catch (InvalidOperationException) { /* invalid state: ignore the click */ } Prevention
- Bind the ready button's Enabled to CanToggleReady(LocalUser.State).
- Re-render controls on every state-change event.
- Do not queue ready toggles across state transitions.
When it happens
Trigger: Calling ToggleReady() while localUser.State is Spectating, Playing, Loaded, or Results. The state machine rejects the transition.
Common situations: Ready button not disabled during a match; spectating user clicks ready; race where the button is clicked as the match starts.
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 spectate when in {localUser.State}
- Cannot join an inactive room.
AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13).
Data as JSON: /api/errors/18893d213aa0c6d4.
Report an issue: GitHub.