{"record":{"id":"9217d162835ade55","repo":"libnyanpasu/clash-nyanpasu","slug":"proxy-snapshot-belongs-to-a-retired-instance","errorCode":null,"errorMessage":"proxy snapshot belongs to a retired instance","messagePattern":"proxy snapshot belongs to a retired instance","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"backend/tauri/src/core/proxies.rs","lineNumber":336,"sourceCode":"        &self,\n        message: impl FnOnce(RpcReplyPort<Result<T>>) -> Message,\n    ) -> Result<T> {\n        match self\n            .0\n            .actor\n            .call(message, Some(Duration::from_secs(120)))\n            .await\n        {\n            Ok(ractor::rpc::CallResult::Success(result)) => result,\n            Ok(ractor::rpc::CallResult::Timeout) => anyhow::bail!(\n                \"proxy actor timed out; an operation may still be running, do not replay mutations automatically\"\n            ),\n            _ => 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    }","sourceCodeStart":318,"sourceCodeEnd":354,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/core/proxies.rs#L318-L354","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Retry the call after a short delay so the actor refreshes its snapshot with the new core instance","Restart or re-select the core so the proxies actor re-binds to a live API before reading proxies","Handle this error in the UI as a transient state and re-fetch after core readiness signals"],"exampleFix":"// before\nlet proxies = proxies_client.get(false).await?;\n// after\nlet proxies = match proxies_client.get(false).await {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"retired instance\") => {\n        tokio::time::sleep(Duration::from_millis(500)).await;\n        proxies_client.get(true).await?\n    }\n    Err(e) => return Err(e),\n};","handlingStrategy":"retry","validationCode":"// caller-side staleness check is not possible before the call; instead guard the retry\nlet proxies = loop {\n    match client.get(false).await {\n        Ok(p) => break p,\n        Err(e) if e.to_string().contains(\"retired instance\") => tokio::time::sleep(Duration::from_millis(500)).await,\n        Err(e) => return Err(e),\n    }\n};","typeGuard":null,"tryCatchPattern":"match proxies_client.get(force).await {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"retired instance\") => /* transient: wait for core ready, then retry */,\n    Err(e) => return Err(e),\n}","preventionTips":["Subscribe to core-restart events and refresh proxy data only after the new API handle is live","Add bounded retry with backoff around proxy reads during core lifecycle transitions","Avoid reading proxies concurrently with core switch/restart operations"],"tags":["rust","actor","stale-state","proxy"],"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"}