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

  1. Read the wrapped `{error:#}` chain for the root cause from feat::patch_verge and fix the offending patch payload.
  2. 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.
  3. Validate the patch fields before calling patch_verge_config (e.g. tun mode, system proxy flags) to catch invalid values early.
  4. 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

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


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/fd35a1bbd6f3eaa6. Report an issue: GitHub.