libnyanpasu/clash-nyanpasu · warning

runtime snapshot changed; refresh the inspection

Error message

runtime snapshot changed; refresh the inspection

What it means

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.

Source

Thrown at backend/tauri/src/client/runtime_inspection.rs:85

                        .any(|log| log.key == node.key && !log.entries.is_empty()),
                    tag: node.tag.clone(),
                    next: node.next.clone().unwrap_or_default(),
                    changed_fields: node
                        .snapshot
                        .changed_fields
                        .as_ref()
                        .map(|fields| fields.iter().cloned().collect()),
                })
                .collect(),
        }
    }

    fn inspection_content(
        &self,
        snapshot_id: &str,
        node_id: u32,
    ) -> anyhow::Result<RuntimeInspectionContent> {
        anyhow::ensure!(
            self.inspection_id == snapshot_id,
            "runtime snapshot changed; refresh the inspection"
        );
        let node = self
            .inspection
            .graph
            .nodes
            .get(node_id as usize)
            .ok_or_else(|| anyhow::anyhow!("runtime snapshot node does not exist"))?;
        let yaml = serde_yaml::to_string(&node.snapshot.config)?;
        Ok(RuntimeInspectionContent {
            diff: self
                .inspection
                .graph
                .comparison_parent(node_id)
                .map(|parent_id| -> anyhow::Result<_> {
                    Ok(RuntimeInspectionDiff {
                        parent_id,

View on GitHub (pinned to f7dbce2997)

Solutions

  1. 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.
  2. Wire the UI to invalidate/clear the inspection panel whenever the runtime config is patched or the core is restarted.
  3. Catch this error in the caller and treat it as a signal to refresh, not as a hard failure.
  4. Pass the snapshot_id straight from the latest `inspect_runtime()` response instead of caching it.

Example fix

// before
let content = client.inspect_runtime_node(&cached_snapshot_id, node_id).await?;
// after
let summary = client.inspect_runtime().await.ok_or_else(|| anyhow!("no runtime inspection"))?;
let content = client.inspect_runtime_node(&summary.snapshot_id, node_id)
    .await
    .or_else(|_| client.inspect_runtime_node(&summary.snapshot_id, node_id))?;
Defensive patterns

Strategy: retry

Validate before calling

// call before inspect_runtime_node
let summary = client.inspect_runtime().await;
let fresh = summary.filter(|s| s.snapshot_id == cached_snapshot_id);
if fresh.is_none() { /* refresh summary first */ }

Type guard

fn snapshot_is_current(summary: &RuntimeInspection, snapshot_id: &str) -> bool {
    summary.snapshot_id == snapshot_id
}

Try / catch

match client.inspect_runtime_node(&snapshot_id, node_id).await {
    Ok(content) => content,
    Err(e) if e.to_string().contains("snapshot changed") => {
        let summary = client.inspect_runtime().await.ok_or_else(|| anyhow!("no inspection"))?;
        client.inspect_runtime_node(&summary.snapshot_id, node_id).await?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

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