libnyanpasu/clash-nyanpasu · error
legacy mutation failed: {error:#}
Error message
legacy mutation failed: {error:#} What it means
Thrown by `run_legacy_verge_mutation` when the legacy mutation closure (e.g. `feat::patch_verge`) returns an error. Since nothing was committed yet, the error is wrapped as a 'partial mutation' result with no restore information (`None`), reporting `legacy mutation failed: {error:#}`. It marks the legacy side-effect compatibility bridge (TODO(actor-migration)) failing before any on-disk state changed.
Source
Thrown at backend/tauri/src/bridge/verge.rs:267
let prepared = self
.legacy_store
.prepare_commit(&managed.legacy_verge_path, state.clone())?;
self.replace_typed_config_from_legacy(state, move || prepared.commit())
.await?;
Ok(())
}
pub async fn run_legacy_verge_mutation<F, Fut>(&self, mutate: F) -> ClientResult<()>
where
F: FnOnce() -> Fut,
Fut: Future<Output = anyhow::Result<()>>,
{
let managed = self.managed()?;
let _guard = managed.verge_update_lock.lock().await;
let previous = self.legacy_store.snapshot()?;
if let Err(error) = mutate().await {
return Err(Self::legacy_mutation_partial(
anyhow::anyhow!("legacy mutation failed: {error:#}"),
None,
));
}
// TODO(actor-migration): compatibility bridge for legacy side-effect writers.
// 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() {View on GitHub (pinned to f7dbce2997)
Solutions
- Read the wrapped `{error:#}` chain for the root cause from feat::patch_verge and fix the offending patch payload.
- If the mutation route should no longer use legacy side effects, switch the patch route to the typed path (`apply_typed_config_patch_plan`) so this bridge is not exercised.
- Validate the patch fields before calling patch_verge_config (e.g. tun mode, system proxy flags) to catch invalid values early.
- No state was committed in this branch — simply correct inputs and retry; no manual restore is needed.
Defensive patterns
Strategy: validation
Validate before calling
// validate the patch payload before invoking the legacy route
fn validate_verge_patch(patch: &IVerge) -> Result<(), String> {
if let Some(tun) = patch.enable_tun_mode {
if !matches!(tun, true | false) { return Err("enable_tun_mode must be bool".into()) }
}
Ok(())
} Type guard
fn is_legacy_mutation_failure(err: &ClientError) -> bool {
err.to_string().starts_with("legacy mutation failed:")
} Try / catch
match bridge.patch_verge_config(payload).await {
Err(e) if e.to_string().starts_with("legacy mutation failed") => {
// nothing committed; surface validation/root-cause to user and retry after fix
ui.report_validation_error(e);
}
other => other?,
} Prevention
- Validate patch fields against the schema before calling patch_verge_config.
- Migrate callers off LegacyVergePatchRoute::LegacySideEffects to the typed plan route.
- Check OS side-effect prerequisites (system proxy privileges, TUN adapter) before patching.
- Log the full `{error:#}` chain, not just the top message, to find the feat::patch_verge root cause.
When it happens
Trigger: Calling `patch_verge_config` routed through `LegacyVergePatchRoute::LegacySideEffects` where the `mutate()` future (`crate::feat::patch_verge`) fails — e.g. invalid patch payload rejected by the legacy writer, OS side-effect failure, or core update error.
Common situations: User submits a verge config patch with values the legacy `feat::patch_verge` cannot apply; system proxy or TUN side effect fails mid-patch; underlying config store inaccessible.
Understand the failure class
Background: "is deprecated and will be removed" — deprecation warnings for old API names, keywords, and options, and how to migrate before the removal release — this error's family across 29 libraries.
Related errors
- {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/fd35a1bbd6f3eaa6.
Report an issue: GitHub.