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
- Read the inner `{error:#}` chain to identify the apply failure (IO, lock, validation).
- Check filesystem permissions/disk space for the app config directory and fix them.
- Ensure only one app instance runs (the bridge serializes via an in-process lock only).
- 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
- Ensure writable config directory and sufficient disk space.
- Run only one app instance; the update lock is in-process only.
- Validate patches against the typed schema before applying.
- After any failure, diff the committed config against intent to repair drift.
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
- cannot repair typed clash config before split_legacy_config
- Invalid theme color: {}
- failed to parse config: {e}
- field `{field}` not found, skipped
- unimplemented!()
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/ce7b97f8586b5b90.
Report an issue: GitHub.