{"record":{"id":"bf7f10eb3ef51c75","repo":"libnyanpasu/clash-nyanpasu","slug":"runtime-snapshot-changed-refresh-the-inspection","errorCode":null,"errorMessage":"runtime snapshot changed; refresh the inspection","messagePattern":"runtime snapshot changed; refresh the inspection","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"backend/tauri/src/client/runtime_inspection.rs","lineNumber":85,"sourceCode":"                        .any(|log| log.key == node.key && !log.entries.is_empty()),\n                    tag: node.tag.clone(),\n                    next: node.next.clone().unwrap_or_default(),\n                    changed_fields: node\n                        .snapshot\n                        .changed_fields\n                        .as_ref()\n                        .map(|fields| fields.iter().cloned().collect()),\n                })\n                .collect(),\n        }\n    }\n\n    fn inspection_content(\n        &self,\n        snapshot_id: &str,\n        node_id: u32,\n    ) -> anyhow::Result<RuntimeInspectionContent> {\n        anyhow::ensure!(\n            self.inspection_id == snapshot_id,\n            \"runtime snapshot changed; refresh the inspection\"\n        );\n        let node = self\n            .inspection\n            .graph\n            .nodes\n            .get(node_id as usize)\n            .ok_or_else(|| anyhow::anyhow!(\"runtime snapshot node does not exist\"))?;\n        let yaml = serde_yaml::to_string(&node.snapshot.config)?;\n        Ok(RuntimeInspectionContent {\n            diff: self\n                .inspection\n                .graph\n                .comparison_parent(node_id)\n                .map(|parent_id| -> anyhow::Result<_> {\n                    Ok(RuntimeInspectionDiff {\n                        parent_id,","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/client/runtime_inspection.rs#L67-L103","documentation":"This error is thrown by `RuntimeSnapshot::inspection_content` when the `snapshot_id` supplied by the caller does not match the id of the currently promoted runtime snapshot. Inspection data is a read-only projection of one immutable, promoted config-build snapshot; once the runtime config is rebuilt (e.g. after a profile switch or config patch), the old snapshot is replaced and any stale id is rejected. The mismatch check exists to prevent the UI from mixing node ids, diffs and logs from an inspection generation that no longer exists.","triggerScenarios":"Calling `NyanpasuClient::inspect_runtime_node(snapshot_id, node_id)` with a snapshot_id obtained from an earlier `inspect_runtime()` call, after the promoted runtime snapshot has been replaced (new config build, profile change, core restart). Even two snapshots with identical content from different lifetimes do not alias — the id check compares lifetime ids, not content.","commonSituations":"A frontend holds a stale snapshot id in UI state while a background config update promotes a new snapshot; a user edits a profile then the inspection panel lazily loads a node using the old id; long-lived UI sessions that never re-fetch the inspection summary after config changes.","solutions":["Re-fetch the current inspection via `inspect_runtime()` to get the fresh snapshot_id and node list, then retry `inspect_runtime_node` with the new id.","Wire the UI to invalidate/clear the inspection panel whenever the runtime config is patched or the core is restarted.","Catch this error in the caller and treat it as a signal to refresh, not as a hard failure.","Pass the snapshot_id straight from the latest `inspect_runtime()` response instead of caching it."],"exampleFix":"// before\nlet content = client.inspect_runtime_node(&cached_snapshot_id, node_id).await?;\n// after\nlet summary = client.inspect_runtime().await.ok_or_else(|| anyhow!(\"no runtime inspection\"))?;\nlet content = client.inspect_runtime_node(&summary.snapshot_id, node_id)\n    .await\n    .or_else(|_| client.inspect_runtime_node(&summary.snapshot_id, node_id))?;","handlingStrategy":"retry","validationCode":"// call before inspect_runtime_node\nlet summary = client.inspect_runtime().await;\nlet fresh = summary.filter(|s| s.snapshot_id == cached_snapshot_id);\nif fresh.is_none() { /* refresh summary first */ }","typeGuard":"fn snapshot_is_current(summary: &RuntimeInspection, snapshot_id: &str) -> bool {\n    summary.snapshot_id == snapshot_id\n}","tryCatchPattern":"match client.inspect_runtime_node(&snapshot_id, node_id).await {\n    Ok(content) => content,\n    Err(e) if e.to_string().contains(\"snapshot changed\") => {\n        let summary = client.inspect_runtime().await.ok_or_else(|| anyhow!(\"no inspection\"))?;\n        client.inspect_runtime_node(&summary.snapshot_id, node_id).await?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never cache snapshot_id across config changes; always read it from the latest inspect_runtime() call","Invalidate inspection UI state on every config patch / profile change / core restart","Treat the error message as a refresh signal, not a fatal failure","Keep the summary and node requests in the same UI interaction cycle"],"tags":["rust","stale-state","snapshot","config-inspection"],"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"}