AprilNEA/OpenLogi · warning

client dropped before the registry resolved

Error message

client dropped before the registry resolved

What it means

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.

Solutions

  1. Treat it as benign shutdown noise: filter or ignore this error in the poll task's result handling.
  2. Ensure the watch task is cancelled/aborted when the client is dropped so it never runs after teardown.
  3. If it fires during normal operation, find what drops the client early (agent disconnect, failed startup) and fix that lifecycle bug.
  4. Log at debug/trace level instead of propagating it as a user-visible failure.

Example fix

// before
let Some(client) = weak.upgrade() else {
    anyhow::bail!("client dropped before the registry resolved");
};
// after
let Some(client) = weak.upgrade() else {
    log::debug!("asset watch cancelled: client dropped before the registry resolved");
    return Ok(());
};
Defensive patterns

Strategy: try-catch

Try / catch

match poll_result {
    Err(e) if e.to_string().contains("client dropped before the registry resolved") => {
        log::debug!("asset watch cancelled during shutdown"); // benign
    }
    Err(e) => log::error!("asset watch failed: {e}"),
    Ok(v) => apply(v),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of AprilNEA/OpenLogi@e846e6f4b4 (2026-09-13). Data as JSON: /api/errors/aec3508da0de87d1. Report an issue: GitHub.

Appendix: source

Thrown at crates/openlogi-desktop/src/services/assets/queries.rs:102

///
/// The returned task owns the subscription. Dropping it unsubscribes, after
/// which the entry follows normal GC.
pub(crate) fn watch_model(
    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.

View on GitHub (pinned to e846e6f4b4)