libnyanpasu/clash-nyanpasu · error
{error:#}
Error message
{error:#} What it means
The bare `{error:#}` message is the top-level wrapper `legacy_mutation_partial` applies to any failure in the post-mutation reconcile phase of `run_legacy_verge_mutation`: preparing/restoring the legacy file, refreshing the legacy projection, fetching clash config, YAML conversion, building the typed patch plan, or the typed commit. The mutation itself succeeded, but rolling forward to typed state (or restoring the file after a partial failure) failed, so the result is a 'partial mutation' — committed legacy state with incomplete typed reconciliation.
Source
Thrown at backend/tauri/src/bridge/verge.rs:290
// Reason: feat::patch_verge still executes OS effects while producing legacy state.
// Remove when: side effects are prepared and committed by typed domain services.
let desired = self.legacy_store.snapshot()?;
let patch = legacy_patch_between(&previous, &desired)?;
// Captured before `patch` is moved into `desired.patch_config(patch)`
// below: the post-commit reconcile must build the runtime config from
// the just-committed typed state, never from the pre-commit draft
// (AGENTS.md section 10: commit first, then side effects).
let reconcile_tun = patch.enable_tun_mode.is_some();
let restore = self
.legacy_store
.prepare_restore(&managed.legacy_verge_path, previous)
.map_err(|error| Self::legacy_mutation_partial(error, None))?;
if let Err(error) = restore.commit() {
return Err(Self::legacy_mutation_partial(error, None));
}
let base = self.refresh_legacy_projection().await.map_err(|error| {
Self::legacy_mutation_partial(anyhow::anyhow!(format!("{error:#}")), Some(error))
})?;
let clash = managed.client.get_clash_config().await.map_err(|error| {
Self::legacy_mutation_partial(anyhow::anyhow!(format!("{error:#}")), Some(error))
})?;
let legacy_clash = super::yaml_convert(&clash.overrides)
.map_err(|error| Self::legacy_mutation_partial(error, None))?;
let plan = Self::typed_patch_plan(base.clone(), &patch, &legacy_clash)
.map_err(|error| Self::legacy_mutation_partial(error, None))?;
let mut desired = base;
desired.patch_config(patch);
let finalize = self
.legacy_store
.prepare_commit(&managed.legacy_verge_path, desired)
.map_err(|error| Self::legacy_mutation_partial(error, None))?;
match self
.apply_typed_config_patch_plan(plan, move || finalize.commit())
.awaitView on GitHub (pinned to f7dbce2997)
Solutions
- Inspect the inner `error:#` chain (and `Some(error)` cause when present) to see which reconcile step failed — projection refresh, clash fetch, yaml_convert, plan build, or commit.
- If the core is not running or IPC failed, restart the core/client and re-run the patch; legacy state is committed but the typed projection is stale.
- Fix malformed clash overrides YAML that breaks `yaml_convert`, or unsupported patch fields missing a typed mapping in `typed_patch_plan`.
- Check filesystem permissions on the verge config path if `prepare_restore`/`restore.commit()`/`prepare_commit` reported the failure.
- Report/verify degraded state to the UI: legacy config was persisted, so avoid blind re-patching with the same payload until the root cause is fixed.
Defensive patterns
Strategy: fallback
Validate before calling
// pre-flight the reconcile dependencies before mutating
async fn reconcile_ready(managed: &Managed) -> anyhow::Result<()> {
managed.client.get_clash_config().await?; // core reachable
let path = &managed.legacy_verge_path;
assert!(path.parent().map(|p| p.is_ok()).unwrap_or(false), "bad config path");
Ok(())
} Type guard
fn has_partial_cause(err: &ClientError) -> Option<&anyhow::Error> {
err.cause() // legacy_mutation_partial embeds the original error when present
} Try / catch
match bridge.patch_verge_config(payload).await {
Err(e) => {
// legacy state may already be committed; do NOT blindly re-patch
match e.cause() {
Some(inner) if inner.to_string().contains("get_clash_config") => restart_core_and_resync().await?,
Some(inner) if inner.to_string().contains("yaml") => fix_overrides_yaml().await?,
_ => ui.report_degraded("patch applied to legacy config; typed reconcile failed"),
}
}
Ok(()) => {},
} Prevention
- Check core/IPC health (get_clash_config) before submitting verge patches that route through the legacy bridge.
- Keep clash overrides YAML valid; run it through a parser before saving.
- Ensure the verge config path is writable and not locked by backups/AV during patching.
- Surface the embedded cause chain to monitoring so each reconcile step's failure is distinguishable.
- After a partial failure, resync the typed projection from committed legacy state instead of re-applying the patch.
When it happens
Trigger: After `mutate()` succeeds: `restore.commit()` fails while unwinding a previous failure, `refresh_legacy_projection()` or `get_clash_config()` errors (core not running / IPC failure), `super::yaml_convert` fails on malformed overrides, `typed_patch_plan` rejects the diff, or `prepare_commit`/`apply_typed_config_patch_plan` fails.
Common situations: mihomo core is down so `get_clash_config` times out; clash overrides YAML is invalid so yaml_convert fails; the verge.yaml file became unwritable mid-operation; a patch field has no typed mapping so the plan builder errors.
Related errors
- legacy mutation failed: {error:#}
- unimplemented!()
- IPC server is already initialized
- application actor call timed out
- clash config actor reply dropped
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/1478c6e483c5f5fa.
Report an issue: GitHub.