{"record":{"id":"a513e80982d4c866","repo":"tonhowtf/omniget","slug":"omnidisc-unwatch-failed","errorCode":null,"errorMessage":"[omnidisc] unwatch failed","messagePattern":"\\[omnidisc\\] unwatch failed","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/lib/stores/omnidisc-stream-store.svelte.ts","lineNumber":289,"sourceCode":"  busy = true;\n  lastError = null;\n  try {\n    await invoke(\"omnidisc_stream_watch\", { userId });\n    if (!watchingIds.includes(userId)) watchingIds = [...watchingIds, userId];\n    return true;\n  } catch (e) {\n    lastError = errorText(e);\n    return false;\n  } finally {\n    busy = false;\n  }\n}\n\nexport async function unwatchStream(userId: string): Promise<void> {\n  try {\n    await invoke(\"omnidisc_stream_unwatch\", { userId });\n  } catch (e) {\n    console.warn(\"[omnidisc] unwatch failed\", errorText(e));\n  } finally {\n    watchingIds = watchingIds.filter((id) => id !== userId);\n  }\n}\n\nexport async function setStreamVolume(userId: string, gain: number): Promise<void> {\n  try {\n    await invoke(\"omnidisc_stream_set_volume\", { userId, gain: Math.min(2, Math.max(0, gain)) });\n  } catch (e) {\n    console.warn(\"[omnidisc] stream volume failed\", errorText(e));\n  }\n}\n\nexport async function refreshStreamStats(): Promise<StreamStats | null> {\n  try {\n    stats = await invoke<StreamStats>(\"omnidisc_stream_stats\");\n    if (stats.publishing) publishing = stats.publishing;\n    return stats;","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src/lib/stores/omnidisc-stream-store.svelte.ts#L271-L307","documentation":"unwatchStream invokes `omnidisc_stream_unwatch` to stop receiving a user's stream. On backend rejection the warning is logged, but the finally block always removes the userId from `watchingIds`, so local state and backend state can diverge. Called from initStream, toggleWatch, and stop (which unwatch all ids during teardown).","triggerScenarios":"Calling unwatchStream(userId) for a userId the backend is not watching, a malformed userId, or when the backend `omnidisc_stream_unwatch` command panics/is unregistered. Also fires during stop() teardown for every watched id.","commonSituations":"Backend already dropped the peer (stream ended server-side) so unwatch finds nothing; duplicate unwatch after a toggle race; stale watch entries after a reconnect.","solutions":["Make the backend unwatch idempotent (no-op + Ok when the id is unknown).","Filter the id from watchingIds before invoking, or reconcile backend watch state via a stats/refresh call after failure.","Log errorText(e) server-side correlation: inspect the Rust error to see if it is 'not watching' vs command-missing.","Deduplicate toggleWatch so unwatch is not issued twice concurrently."],"exampleFix":"// before\nawait invoke(\"omnidisc_stream_unwatch\", { userId });\n// after\nif (watchingIds.includes(userId)) {\n  await invoke(\"omnidisc_stream_unwatch\", { userId });\n}","handlingStrategy":"try-catch","validationCode":"if (!watchingIds.includes(userId)) return;","typeGuard":null,"tryCatchPattern":"try {\n  await invoke(\"omnidisc_stream_unwatch\", { userId });\n} catch (e) {\n  console.warn(\"[omnidisc] unwatch failed\", errorText(e));\n} finally {\n  watchingIds = watchingIds.filter((id) => id !== userId);\n}","preventionTips":["Keep local watchingIds and backend watch state reconciled via periodic stats refresh.","Make backend unwatch a no-op for unknown ids.","Avoid issuing unwatch while a watch for the same id is still in flight.","Deduplicate teardown loops (stop() unwatching ids already being unwatched)."],"tags":["tauri","ipc","streaming","unwatch"],"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"}