{"record":{"id":"cd51548da9f5ca77","repo":"libnyanpasu/clash-nyanpasu","slug":"core-instance-retired-during-proxy-assembly","errorCode":null,"errorMessage":"core instance retired during proxy assembly","messagePattern":"core instance retired during proxy assembly","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"backend/tauri/src/core/proxies.rs","lineNumber":119,"sourceCode":"        let result = async {\n            let (proxies, providers) = api.proxy_snapshot().await?;\n            let proxies = api::ProxiesRes {\n                proxies: proxies\n                    .into_iter()\n                    .map(|(name, proxy)| (name.as_str().to_owned(), proxy_item(proxy)))\n                    .collect(),\n            };\n            let providers = api::ProvidersProxiesRes {\n                providers: providers\n                    .into_iter()\n                    .map(|(name, provider)| {\n                        Ok((name.as_str().to_owned(), provider_item(provider)?))\n                    })\n                    .collect::<Result<_>>()?,\n            };\n            let proxies = Proxies::from_responses(proxies, providers.clone())?;\n            let fingerprint = serde_json::to_vec(&(&proxies, &providers))?;\n            anyhow::ensure!(\n                !api.is_revoked(),\n                \"core instance retired during proxy assembly\"\n            );\n            Ok::<_, anyhow::Error>(Arc::new(Snapshot {\n                api: api.clone(),\n                proxies,\n                providers,\n                fetched: Instant::now(),\n                fingerprint,\n            }))\n        }\n        .await;\n        match result {\n            Ok(snapshot) => {\n                let changed = self\n                    .cache\n                    .as_ref()\n                    .is_none_or(|old| old.fingerprint != snapshot.fingerprint);","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/core/proxies.rs#L101-L137","documentation":"The proxies subsystem builds a snapshot (proxies + providers) against a mihomo/clash external-controller API instance. Because assembly can take time (multiple HTTP calls), the core instance may have been retired (restarted/replaced) meanwhile; api.is_revoked() detects this and anyhow::ensure! aborts with this error instead of publishing a stale snapshot. It is an intentional staleness guard, not a corruption.","triggerScenarios":"Calling refresh() (directly or through read()/select()) while the core process is being restarted or the API handle is replaced concurrently — the fetched proxy data is then discarded rather than committed.","commonSituations":"User switches cores or triggers a core restart while the proxies page is loading; hot-reload of config tears down the old controller API mid-refresh; rapid successive proxy selections racing with a core restart.","solutions":["Simply retry the operation — after the core settles, a fresh refresh() will succeed against the new instance","Serialize core restarts with proxy refreshes so they don't interleave","Check what triggered a core restart during refresh (config change, hotkey, update) and stagger it","If it recurs constantly, look for a loop restarting the core (crash-restart cycle) and fix that root cause","Treat as transient: UI layers typically show stale data and re-fetch rather than reporting failure"],"exampleFix":"// caller-side retry\nfor _ in 0..3 {\n    match proxies.refresh().await {\n        Ok(snap) => break snap,\n        Err(e) if e.to_string().contains(\"retired\") => continue,\n        Err(e) => return Err(e),\n    }\n}","handlingStrategy":"retry","validationCode":"// before an expensive refresh, bail early if the instance is already revoked\nif api.is_revoked() {\n    return Err(anyhow::anyhow!(\"core instance retired before refresh\"));\n}","typeGuard":null,"tryCatchPattern":"match proxies.refresh().await {\n    Ok(snap) => use(snap),\n    Err(e) if e.to_string().contains(\"retired during proxy assembly\") => {\n        tokio::time::sleep(Duration::from_millis(200)).await;\n        proxies.refresh().await?; // retry against the new core instance\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Avoid triggering core restarts while proxy data is loading","Serialize config changes and core restarts with proxy refreshes","Add small backoff + retry for transient staleness errors in UI data loading","Watch for crash-restart loops that retire instances frequently"],"tags":["concurrency","stale-state","proxies","race-condition"],"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"}