libnyanpasu/clash-nyanpasu · warning

provider snapshot belongs to a retired instance

Error message

provider snapshot belongs to a retired instance

What it means

ProxiesClient::providers fetches a provider snapshot from the proxies actor and validates that the snapshot's api handle is not revoked. If the underlying core instance was replaced/restarted after the snapshot was taken, the snapshot belongs to a retired instance and is rejected, preventing stale provider data from being served.

Source

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

            _ => 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())
    }
    pub async fn select(&self, group: String, name: String, interrupt: bool) -> Result<()> {
        self.call(|reply| Message::Select {
            group,
            name,
            interrupt,
            reply,
        })
        .await
    }
    pub async fn update_provider(&self, name: String) -> Result<()> {
        self.call(|reply| Message::UpdateProvider { name, reply })
            .await
    }

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Retry after the core becomes ready so the actor obtains a fresh snapshot
  2. Trigger a proxy actor refresh/rebind to the live API before querying providers
  3. Treat as transient in callers and surface a retry affordance to the user

Example fix

// before
let providers = proxies_client.providers().await?;
// after
let providers = retry_on_retired(|| proxies_client.providers(), 3, Duration::from_millis(500)).await?;
Defensive patterns

Strategy: retry

Validate before calling

// ensure core is ready before reading providers
if core_client.status().await? != CoreStatus::Running { core_client.wait_ready(Duration::from_secs(5)).await?; }
let providers = proxies_client.providers().await?;

Try / catch

match proxies_client.providers().await {
    Ok(p) => p,
    Err(e) if e.to_string().contains("retired instance") => /* refresh/retry after core ready */,
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `proxies_client.providers()` after a core restart/replace or while the external controller handle in the cached snapshot reports is_revoked() == true.

Common situations: Provider list refresh in the UI racing a core update or restart; switching core types; core process crash with actor holding the old snapshot.

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/cd2f5e8e7a8d6f5d. Report an issue: GitHub.