libnyanpasu/clash-nyanpasu · warning

proxy snapshot belongs to a retired instance

Error message

proxy snapshot belongs to a retired instance

What it means

ProxiesClient::get requests a proxy snapshot from the proxies actor via an RPC message. The returned snapshot carries an `api` handle to the mihomo/clash external controller instance; if that instance has since been revoked (core restarted/replaced, actor re-initialized), the snapshot is stale and the client refuses to return it. This is a staleness guard ensuring callers never see proxy data from a dead core instance.

Source

Thrown at backend/tauri/src/core/proxies.rs:336

        &self,
        message: impl FnOnce(RpcReplyPort<Result<T>>) -> Message,
    ) -> Result<T> {
        match self
            .0
            .actor
            .call(message, Some(Duration::from_secs(120)))
            .await
        {
            Ok(ractor::rpc::CallResult::Success(result)) => result,
            Ok(ractor::rpc::CallResult::Timeout) => anyhow::bail!(
                "proxy actor timed out; an operation may still be running, do not replay mutations automatically"
            ),
            _ => anyhow::bail!("proxy actor is unavailable"),
        }
    }
    pub async fn get(&self, force: bool) -> Result<Proxies> {
        let snapshot = self.call(|reply| Message::Read { force, reply }).await?;
        anyhow::ensure!(
            !snapshot.api.is_revoked(),
            "proxy snapshot belongs to a retired instance"
        );
        Ok(snapshot.proxies.clone())
    }
    pub async fn providers(&self) -> Result<api::ProvidersProxiesRes> {
        let snapshot = self
            .call(|reply| Message::Read {
                force: false,
                reply,
            })
            .await?;
        anyhow::ensure!(
            !snapshot.api.is_revoked(),
            "provider snapshot belongs to a retired instance"
        );
        Ok(snapshot.providers.clone())
    }

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Retry the call after a short delay so the actor refreshes its snapshot with the new core instance
  2. Restart or re-select the core so the proxies actor re-binds to a live API before reading proxies
  3. Handle this error in the UI as a transient state and re-fetch after core readiness signals

Example fix

// before
let proxies = proxies_client.get(false).await?;
// after
let proxies = match proxies_client.get(false).await {
    Ok(p) => p,
    Err(e) if e.to_string().contains("retired instance") => {
        tokio::time::sleep(Duration::from_millis(500)).await;
        proxies_client.get(true).await?
    }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: retry

Validate before calling

// caller-side staleness check is not possible before the call; instead guard the retry
let proxies = loop {
    match client.get(false).await {
        Ok(p) => break p,
        Err(e) if e.to_string().contains("retired instance") => tokio::time::sleep(Duration::from_millis(500)).await,
        Err(e) => return Err(e),
    }
};

Try / catch

match proxies_client.get(force).await {
    Ok(p) => p,
    Err(e) if e.to_string().contains("retired instance") => /* transient: wait for core ready, then retry */,
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `client.proxies().get(force)` after the core process was restarted or replaced, or while the proxies actor is re-initializing its API handle; the actor replies with a cached snapshot whose embedded api handle reports is_revoked() == true.

Common situations: Core switch (mihomo <-> clash-rs), core crash and auto-restart, config reload that recreates the external controller, or a UI refresh racing a core restart.

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 libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/9217d162835ade55. Report an issue: GitHub.