{"record":{"id":"cd2f5e8e7a8d6f5d","repo":"libnyanpasu/clash-nyanpasu","slug":"provider-snapshot-belongs-to-a-retired-instance","errorCode":null,"errorMessage":"provider snapshot belongs to a retired instance","messagePattern":"provider snapshot belongs to a retired instance","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"backend/tauri/src/core/proxies.rs","lineNumber":349,"sourceCode":"            _ => anyhow::bail!(\"proxy actor is unavailable\"),\n        }\n    }\n    pub async fn get(&self, force: bool) -> Result<Proxies> {\n        let snapshot = self.call(|reply| Message::Read { force, reply }).await?;\n        anyhow::ensure!(\n            !snapshot.api.is_revoked(),\n            \"proxy snapshot belongs to a retired instance\"\n        );\n        Ok(snapshot.proxies.clone())\n    }\n    pub async fn providers(&self) -> Result<api::ProvidersProxiesRes> {\n        let snapshot = self\n            .call(|reply| Message::Read {\n                force: false,\n                reply,\n            })\n            .await?;\n        anyhow::ensure!(\n            !snapshot.api.is_revoked(),\n            \"provider snapshot belongs to a retired instance\"\n        );\n        Ok(snapshot.providers.clone())\n    }\n    pub async fn select(&self, group: String, name: String, interrupt: bool) -> Result<()> {\n        self.call(|reply| Message::Select {\n            group,\n            name,\n            interrupt,\n            reply,\n        })\n        .await\n    }\n    pub async fn update_provider(&self, name: String) -> Result<()> {\n        self.call(|reply| Message::UpdateProvider { name, reply })\n            .await\n    }","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/core/proxies.rs#L331-L367","documentation":"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.","triggerScenarios":"Calling `proxies_client.providers()` after a core restart/replace or while the external controller handle in the cached snapshot reports is_revoked() == true.","commonSituations":"Provider list refresh in the UI racing a core update or restart; switching core types; core process crash with actor holding the old snapshot.","solutions":["Retry after the core becomes ready so the actor obtains a fresh snapshot","Trigger a proxy actor refresh/rebind to the live API before querying providers","Treat as transient in callers and surface a retry affordance to the user"],"exampleFix":"// before\nlet providers = proxies_client.providers().await?;\n// after\nlet providers = retry_on_retired(|| proxies_client.providers(), 3, Duration::from_millis(500)).await?;","handlingStrategy":"retry","validationCode":"// ensure core is ready before reading providers\nif core_client.status().await? != CoreStatus::Running { core_client.wait_ready(Duration::from_secs(5)).await?; }\nlet providers = proxies_client.providers().await?;","typeGuard":null,"tryCatchPattern":"match proxies_client.providers().await {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"retired instance\") => /* refresh/retry after core ready */,\n    Err(e) => return Err(e),\n}","preventionTips":["Gate provider reads on core readiness signals","Retry transient snapshot staleness errors with a small backoff","Invalidate cached provider views when the core instance changes"],"tags":["rust","actor","stale-state","proxy-provider"],"backgroundTag":"invalid-state-transition","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}