AprilNEA/OpenLogi · warning
{e}
Error message
{e} What it means
In the desktop asset service's `watch_model`, a deferred async closure resolves the asset registry through a weak client handle. If the client was dropped before the registry resolved (`weak.upgrade()` returns `None`), the closure bails with this message. It is a lifecycle race: the model-watch outlived the client that started it.
Solutions
- Treat it as benign cancellation during teardown — drop/skip the outcome instead of surfacing it as a user error.
- If it appears in normal operation, check that the service keeps a strong client reference for the lifetime of the watch.
- Ensure watches are cancelled (the handle dropped) before the client is released.
- Re-open/re-poll the assets view; the next watch with a live client will sync normally.
Example fix
// before
let Some(client) = weak.upgrade() else {
anyhow::bail!("client dropped before the registry resolved");
};
// after — treat teardown as a no-op, not an error
let Some(client) = weak.upgrade() else {
return Ok(()); // client gone: watch cancelled during teardown
}; Defensive patterns
Strategy: type-guard
Type guard
// treat a dropped client as normal cancellation, not an error
let Some(client) = weak.upgrade() else {
tracing::debug!("asset watch cancelled: client dropped");
return Ok(());
}; Prevention
- Hold a strong client reference for the lifetime of any spawned watch task.
- Cancel watches (drop their handles) before releasing the client during teardown.
- Filter this outcome out of user-visible error reporting; log it at debug level.
When it happens
Trigger: A model-watch task is spawned with a `Weak` client; by the time the async closure runs, the strong client has been released (window/service torn down), so `weak.upgrade()` yields `None` and `anyhow::bail!("client dropped before the registry resolved")` fires.
Common situations: Rapid window close/open cycles during startup or shutdown; a settings change triggering a re-watch while the old watch is still in flight; agent disconnect causing the service to release its client while pending watches are queued.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of AprilNEA/OpenLogi@e846e6f4b4 (2026-09-13).
Data as JSON: /api/errors/0f2371fa8b693fe5.
Report an issue: GitHub.
Appendix: source
Thrown at crates/openlogi-desktop/src/services/assets/queries.rs:106
client: &SwrClient,
preference: AssetSourcePreference,
target: AssetTarget,
tx: UnboundedSender<bool>,
cx: &AsyncApp,
) -> Task<()> {
let weak = client.downgrade();
let handle = client.subscribe(
(ROOT, "model", model_key(&target)),
move |_| {
let weak = weak.clone();
let target = target.clone();
async move {
let Some(client) = weak.upgrade() else {
anyhow::bail!("client dropped before the registry resolved");
};
let registry = registry(&client, preference)
.await
.map_err(|e| anyhow::anyhow!("{e}"))?;
sync_target(®istry, &target)
}
},
default_options(),
);
settled_outcomes(handle, tx, "asset sync", cx)
}
/// Watch the registry on its own, for the window before any device has appeared.
///
/// The old scheduler special-cased this ("fetch the index even with no
/// devices") so resolution works the moment a first device shows up. Once
/// devices exist their own entries depend on this one, and this subscription
/// just keeps it warm.
pub(crate) fn watch_index(
client: &SwrClient,
preference: AssetSourcePreference,
tx: UnboundedSender<bool>,View on GitHub (pinned to e846e6f4b4)