ppy/osu · warning · InvalidOperationException

Cannot toggle ready when in

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.

Solutions

  1. Only expose the ready toggle when localUser.State is Idle or Ready.
  2. Bind the button's Enabled state to a check on MultiplayerClient.LocalUser?.State.
  3. 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

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


AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13). Data as JSON: /api/errors/18893d213aa0c6d4. Report an issue: GitHub.

Appendix: 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)