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

  1. Treat it as benign cancellation during teardown — drop/skip the outcome instead of surfacing it as a user error.
  2. If it appears in normal operation, check that the service keeps a strong client reference for the lifetime of the watch.
  3. Ensure watches are cancelled (the handle dropped) before the client is released.
  4. 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

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(&registry, &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)