{"record":{"id":"18893d213aa0c6d4","repo":"ppy/osu","slug":"cannot-toggle-ready-when-in-localuser-state","errorCode":null,"errorMessage":"Cannot toggle ready when in {localUser.State}","messagePattern":"Cannot toggle ready when in (.+?)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"warning","filePath":"osu.Game/Online/Multiplayer/MultiplayerClient.cs","lineNumber":447,"sourceCode":"        public async Task ToggleReady()\r\n        {\r\n            var localUser = LocalUser;\r\n\r\n            if (localUser == null)\r\n                return;\r\n\r\n            switch (localUser.State)\r\n            {\r\n                case MultiplayerUserState.Idle:\r\n                    await ChangeState(MultiplayerUserState.Ready).ConfigureAwait(false);\r\n                    return;\r\n\r\n                case MultiplayerUserState.Ready:\r\n                    await ChangeState(MultiplayerUserState.Idle).ConfigureAwait(false);\r\n                    return;\r\n\r\n                default:\r\n                    throw new InvalidOperationException($\"Cannot toggle ready when in {localUser.State}\");\r\n            }\r\n        }\r\n\r\n        /// <summary>\r\n        /// Toggles the <see cref=\"LocalUser\"/>'s spectating state.\r\n        /// </summary>\r\n        /// <exception cref=\"InvalidOperationException\">If a toggle of the spectating state is not valid at this time.</exception>\r\n        public async Task ToggleSpectate()\r\n        {\r\n            var localUser = LocalUser;\r\n\r\n            if (localUser == null)\r\n                return;\r\n\r\n            switch (localUser.State)\r\n            {\r\n                case MultiplayerUserState.Idle:\r\n                case MultiplayerUserState.Ready:\r","sourceCodeStart":429,"sourceCodeEnd":465,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game/Online/Multiplayer/MultiplayerClient.cs#L429-L465","documentation":"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.","triggerScenarios":"Calling ToggleReady() while localUser.State is Spectating, Playing, Loaded, or Results. The state machine rejects the transition.","commonSituations":"Ready button not disabled during a match; spectating user clicks ready; race where the button is clicked as the match starts.","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)."],"exampleFix":"// before\nreadyButton.Action = () => client.ToggleReady();\n\n// after\nreadyButton.Action = () =>\n{\n    var s = client.LocalUser?.State;\n    if (s == MultiplayerUserState.Idle || s == MultiplayerUserState.Ready)\n        client.ToggleReady();\n};","handlingStrategy":"validation","validationCode":"var s = client.LocalUser?.State;\nif (s == MultiplayerUserState.Idle || s == MultiplayerUserState.Ready)\n    await client.ToggleReady();","typeGuard":"bool CanToggleReady(MultiplayerUserState? s)\n    => s == MultiplayerUserState.Idle || s == MultiplayerUserState.Ready;","tryCatchPattern":"try { await client.ToggleReady(); }\ncatch (InvalidOperationException) { /* invalid state: ignore the click */ }","preventionTips":["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."],"tags":["multiplayer","state-machine","ready","invalid-operation"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}