libnyanpasu/clash-nyanpasu · error

compensation fenced by diverged target at {}

Error message

compensation fenced by diverged target at {}

What it means

During journal-based compensation of an aborted profile-file operation, the transaction framework attempts to restore the managed file from its backup. Before overwriting, it verifies the current content hash of the managed path against the hash recorded in the journal. If the file has changed since the transaction started ('diverged'), the framework refuses to blindly roll back and bails with this error, because restoring the backup would clobber changes the user or another process made after the transaction began.

Source

Thrown at backend/tauri/src/service/profile_file.rs:1706

        )
    }

    fn compensate(&self, prepared: &PreparedMaterialization) -> anyhow::Result<()> {
        let root = self.ensure_materialization_layout()?;
        let operation_id = prepared.operation_id();
        let Some((mut location, journal)) = self.locate_materialization(&root, operation_id)?
        else {
            return Ok(());
        };
        let compensating = location.compensating();
        if location != compensating {
            Self::transition_journal(&root, operation_id, location, compensating)?;
            location = compensating;
        }

        let target = self.resolve(&journal.managed_path)?;
        if !self.restore_backup(&root, operation_id, &target, &journal.hash)? {
            bail!(
                "compensation fenced by diverged target at {}",
                journal.managed_path
            );
        }
        Self::remove_operation_artifacts(
            &root,
            operation_id,
            &Self::journal_path(&root, location, operation_id),
        )
    }

    fn prepare_cleanup(
        &self,
        path: &ManagedProfilePath,
        expected_revision: u64,
    ) -> anyhow::Result<PreparedCleanup> {
        let root = self.ensure_materialization_layout()?;
        let target = self.resolve(path)?;

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Let the user resolve the divergence manually: inspect journal.managed_path, keep or merge the new content, then clear/remove the operation journal artifacts so compensation is no longer fenced
  2. Re-run compensation only after confirming the target content is back to the journaled state (e.g. revert the external edit or restore from the newer source deliberately)
  3. Investigate the concurrent writer: ensure only one operation mutates the managed path at a time (operation-id fencing, file locking) and retry the operation in a fresh transaction
  4. If intentionally discarding local changes, remove the journal/backup artifacts for the operation so the stale compensation is skipped instead of failing

Example fix

// before: blind compensation fails on diverged target
compensate(operation_id)?;
// after: check divergence and ask user / drop the journal
if !journal_target_matches(operation_id)? {
    prompt_user_to_resolve(journal.managed_path)?;
    discard_operation_journal(operation_id)?;
} else {
    compensate(operation_id)?;
}
Defensive patterns

Strategy: validation

Validate before calling

fn target_matches_journal(journal: &Journal, root: &Path) -> anyhow::Result<bool> {
    let current = file_sha256(&journal.managed_path)?;
    Ok(current == journal.hash)
}
// call compensate only if target_matches_journal(...)? else resolve manually

Try / catch

match compensate(op_id) {
    Err(e) if e.to_string().contains("fenced by diverged target") => {
        // surface conflict to user; do NOT retry blindly
        prompt_manual_resolution(journal.managed_path)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling the compensation/rollback path (e.g. after a failed materialization or explicit cancel) when the file at journal.managed_path was modified after the journal recorded its hash. Specifically, restore_backup(&root, operation_id, &target, &journal.hash) returns false because the on-disk content of the target no longer matches journal.hash.

Common situations: The user (or an external editor/sync tool) edited the profile YAML while a long-running operation (download, import, patch) was in flight and later aborted; a concurrent transaction wrote to the same managed path; clock/sync tools like cloud drive clients rewrote the file; retrying compensation after a partial previous restore.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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