{"record":{"id":"aec3508da0de87d1","repo":"AprilNEA/OpenLogi","slug":"client-dropped-before-the-registry-resolved","errorCode":null,"errorMessage":"client dropped before the registry resolved","messagePattern":"client dropped before the registry resolved","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/openlogi-desktop/src/services/assets/queries.rs","lineNumber":102,"sourceCode":"///\n/// The returned task owns the subscription. Dropping it unsubscribes, after\n/// which the entry follows normal GC.\npub(crate) fn watch_model(\n    client: &SwrClient,\n    preference: AssetSourcePreference,\n    target: AssetTarget,\n    tx: UnboundedSender<bool>,\n    cx: &AsyncApp,\n) -> Task<()> {\n    let weak = client.downgrade();\n    let handle = client.subscribe(\n        (ROOT, \"model\", model_key(&target)),\n        move |_| {\n            let weak = weak.clone();\n            let target = target.clone();\n            async move {\n                let Some(client) = weak.upgrade() else {\n                    anyhow::bail!(\"client dropped before the registry resolved\");\n                };\n                let registry = registry(&client, preference)\n                    .await\n                    .map_err(|e| anyhow::anyhow!(\"{e}\"))?;\n                sync_target(&registry, &target)\n            }\n        },\n        default_options(),\n    );\n    settled_outcomes(handle, tx, \"asset sync\", cx)\n}\n\n/// Watch the registry on its own, for the window before any device has appeared.\n///\n/// The old scheduler special-cased this (\"fetch the index even with no\n/// devices\") so resolution works the moment a first device shows up. Once\n/// devices exist their own entries depend on this one, and this subscription\n/// just keeps it warm.","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/AprilNEA/OpenLogi/blob/e846e6f4b4405e33bd6a9aaf949a482ce34cb6d8/crates/openlogi-desktop/src/services/assets/queries.rs#L84-L120","documentation":"Inside `watch_model`, an async callback captured only a `Weak` handle to the client entity. When the callback finally runs, `weak.upgrade()` returned `None` because the client was dropped, so the poll task cannot resolve the asset registry and bails instead of panicking. It is an expected-shutdown condition surfaced as an error rather than a crash.","triggerScenarios":"The desktop app closes a services/assets client (e.g. a view is unmounted or the app shuts down) while a spawned watch/poll task for `(ROOT, \"model\", ...)` is still pending; on its next tick the task upgrades the weak handle, fails, and returns this error.","commonSituations":"Rapid navigation away from the device-assets view before the registry resolves; quitting the app with in-flight asset queries; the agent connection being torn down, dropping the client that owned the poll task.","solutions":["Treat it as benign shutdown noise: filter or ignore this error in the poll task's result handling.","Ensure the watch task is cancelled/aborted when the client is dropped so it never runs after teardown.","If it fires during normal operation, find what drops the client early (agent disconnect, failed startup) and fix that lifecycle bug.","Log at debug/trace level instead of propagating it as a user-visible failure."],"exampleFix":"// before\nlet Some(client) = weak.upgrade() else {\n    anyhow::bail!(\"client dropped before the registry resolved\");\n};\n// after\nlet Some(client) = weak.upgrade() else {\n    log::debug!(\"asset watch cancelled: client dropped before the registry resolved\");\n    return Ok(());\n};","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match poll_result {\n    Err(e) if e.to_string().contains(\"client dropped before the registry resolved\") => {\n        log::debug!(\"asset watch cancelled during shutdown\"); // benign\n    }\n    Err(e) => log::error!(\"asset watch failed: {e}\"),\n    Ok(v) => apply(v),\n}","preventionTips":["Abort watch tasks when their owning entity is dropped (use the weak handle as a cancellation signal)","Keep the client alive until the first registry resolution completes before closing views","Log this condition at debug level so shutdown noise is not escalated"],"tags":["gpui","async","lifecycle","desktop"],"backgroundTag":"internal-invariant-violation","analyzedSha":"e846e6f4b4405e33bd6a9aaf949a482ce34cb6d8","analyzedAt":"2026-09-13T03:07:16.451Z","contentChangedAt":"2026-09-13T03:07:16.451Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}