{"record":{"id":"56a8bd7a140764fa","repo":"tonhowtf/omniget","slug":"omnidisc-voice-leave-failed","errorCode":null,"errorMessage":"[omnidisc] voice leave failed","messagePattern":"\\[omnidisc\\] voice leave failed","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/lib/stores/omnidisc-voice-store.svelte.ts","lineNumber":579,"sourceCode":"    outputError = result.output_error ?? null;\n    syncStatsPolling();\n    return true;\n  } catch (e) {\n    lastError = errorText(e);\n    connState = \"failed\";\n    syncStatsPolling();\n    return false;\n  } finally {\n    busy = false;\n  }\n}\n\nexport async function leaveVoice(): Promise<void> {\n  busy = true;\n  try {\n    await invoke(\"omnidisc_voice_leave\");\n  } catch (e) {\n    console.warn(\"[omnidisc] voice leave failed\", errorText(e));\n  } finally {\n    busy = false;\n    session = null;\n    connState = \"idle\";\n    speaking = {};\n    quality = \"unknown\";\n    lastError = null;\n    micError = null;\n    outputError = null;\n    syncStatsPolling();\n  }\n}\n\nexport async function retryVoice(): Promise<boolean> {\n  const target = session?.channelId;\n  if (!target) return false;\n  return joinVoice(target);\n}","sourceCodeStart":561,"sourceCodeEnd":597,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src/lib/stores/omnidisc-voice-store.svelte.ts#L561-L597","documentation":"leaveVoice invokes the backend command `omnidisc_voice_leave` to disconnect from the voice session. On rejection (no active session backend-side, command panic, or missing command), the warning is logged, but the finally block unconditionally clears session, connState, speaking, and quality — so the UI shows disconnected even if the backend did not actually leave.","triggerScenarios":"Calling leaveVoice() when the backend has no active voice session (already left, connection dropped), the Rust `omnidisc_voice_leave` command panics (e.g. connection object already closed), or frontend/backend command-name skew.","commonSituations":"Closing the app/window while voice is connecting; network drop causing the backend to clean up first; double-clicking the leave button; stale session after a reconnect replaced the backend connection.","solutions":["Make the backend leave idempotent: Ok when there is no session to leave.","Guard the frontend: skip the invoke when session is null (local state reset only).","Reconcile after failure by re-fetching `omnidisc_voice_status` to confirm actual connection state.","Debounce the leave button to prevent duplicate concurrent invokes."],"exampleFix":"// before\nawait invoke(\"omnidisc_voice_leave\");\n// after\nif (session !== null) {\n  try { await invoke(\"omnidisc_voice_leave\"); }\n  finally { /* reset local state */ }\n}","handlingStrategy":"try-catch","validationCode":"if (session === null) { resetLocalVoiceState(); return; }","typeGuard":null,"tryCatchPattern":"busy = true;\ntry {\n  await invoke(\"omnidisc_voice_leave\");\n} catch (e) {\n  console.warn(\"[omnidisc] voice leave failed\", errorText(e));\n  await reconcileVoiceStatus(); // re-fetch omnidisc_voice_status\n} finally {\n  busy = false;\n  session = null;\n  connState = \"idle\";\n}","preventionTips":["Skip the invoke when there is no local session; just reset state.","Make backend leave idempotent for already-closed sessions.","Debounce the leave control to prevent duplicate concurrent invokes.","After a failed leave, re-fetch voice status to confirm the true connection state."],"tags":["tauri","ipc","voice","leave"],"backgroundTag":"api-request-failed","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}