libnyanpasu/clash-nyanpasu · error · ClientError

legacy mutation may have non-reversible side effects and req

Error message

legacy mutation may have non-reversible side effects and requires reconciliation: {error:#}

What it means

Raised on the `Err(error)` branch of `apply_typed_config_patch_plan` inside `run_legacy_verge_mutation`: the typed configuration patch plan failed to apply. It is wrapped by `legacy_mutation_partial`, so the message always carries the prefix "legacy mutation may have non-reversible side effects and requires reconciliation", meaning the legacy store was already mutated by `mutate()` and partially restored/re-committed, leaving state possibly inconsistent with the typed config.

Source

Thrown at backend/tauri/src/bridge/verge.rs:333

                    // (feat::patch_verge) would have reconciled against
                    // before this commit ran. A reconcile failure here must
                    // not undo the successful commit: report it the same way
                    // the commit-phase failures above already do.
                    managed
                        .client
                        .rebuild_running_config()
                        .await
                        .map_err(|error| {
                            Self::legacy_mutation_partial(
                                anyhow::anyhow!(format!("{error:#}")),
                                Some(error),
                            )
                        })?;
                }
                Ok(())
            }
            Err(error) => Err(Self::legacy_mutation_partial(
                anyhow::anyhow!(format!("{error:#}")),
                Some(error),
            )),
        }
    }

    fn legacy_mutation_partial(error: anyhow::Error, source: Option<ClientError>) -> ClientError {
        let message = format!(
            "legacy mutation may have non-reversible side effects and requires reconciliation: {error:#}"
        );
        if let Some(ClientError::PartialCommit(partial)) = source {
            return partial.with_legacy_state_uncertain(message).into();
        }

        let primary = ClientError::Anyhow(error);
        PartialCommit::new(&primary, Vec::new(), Vec::new(), Vec::new())
            .with_legacy_state_uncertain(message)
            .into()
    }

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Read the inner `{error:#}` chain to identify the apply failure (IO, lock, validation).
  2. Check filesystem permissions/disk space for the app config directory and fix them.
  3. Ensure only one app instance runs (the bridge serializes via an in-process lock only).
  4. Re-run the patch; because the error flags legacy state uncertainty, compare the verge config against the intended patch and correct any drifted fields.

Example fix

// before: fire-and-forget patch that ignores partial state
spawn(patch_verge_config(patch));

// after: await and reconcile on partial-commit failure
if let Err(e) = client.patch_verge_config(patch).await {
    log::warn!("patch failed, reconciling: {e:#}");
    let current = client.get_app_config().await?;
    // verify/correct drifted legacy fields here
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight checks before patching
let cfg_path = app_config_dir().join("verge.yaml");
anyhow::ensure!(cfg_path.parent().map(|p| p.exists()).unwrap_or(false), "config dir missing");
let meta = std::fs::metadata(&cfg_path)?;
anyhow::ensure!(!meta.permissions().readonly(), "verge config is read-only");

Try / catch

if let Err(e) = client.patch_verge_config(patch).await {
    if e.to_string().contains("non-reversible side effects") {
        // verify on-disk state vs intended patch and repair drift
        let actual = client.get_app_config().await?;
        log::warn!("drift check needed after failed patch: {e:#}; actual={actual:?}");
    } else {
        return Err(e.into());
    }
}

Prevention

When it happens

Trigger: Calling `patch_verge_config` (or any wrapper of `run_legacy_verge_mutation`) where the computed typed patch plan fails during application: a lock contention failure, an I/O failure writing the typed config, a commit finalize error, or a validation rejection inside `apply_typed_config_patch_plan`.

Common situations: Config file on disk became read-only or locked by another process; concurrent patch requests raced despite the update lock (e.g. multiple app instances); the patch plan referenced keys rejected by the typed layer; disk full during the commit write.

Related errors


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