libnyanpasu/clash-nyanpasu · error

active materialization target diverged before recovery

Error message

active materialization target diverged before recovery

What it means

During recovery of a materialization transaction, the code checks whether revision counters advanced past the journaled revision while the target file is still in its pre-promote state. If the target hash differs from what the journal recorded for the pre-promote state (target_is_pre_promote fails), the on-disk state is inconsistent with any journaled history — the transaction was superseded, rolled back, or someone edited the file — and recovery bails with this error instead of applying a stale staged file over diverged content.

Source

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

                        if active {
                            match actual_location {
                                JournalLocation::StatePrepared
                                | JournalLocation::StatePromoting => {
                                    if target_matches {
                                        if actual_location == JournalLocation::StatePromoting {
                                            self.complete(&prepared)?;
                                            return Ok((0, 0, 1, 0));
                                        }
                                        Self::discard_materialization(
                                            &root,
                                            &operation_id,
                                            actual_location,
                                        )?;
                                        return Ok((1, 0, 0, 0));
                                    }
                                    if !Self::target_is_pre_promote(&root, &operation_id, &target)?
                                    {
                                        bail!(
                                            "active materialization target diverged before recovery"
                                        );
                                    }
                                    // revision advanced past this journal while the target is
                                    // still pre-promote: the transaction was superseded or
                                    // rolled back. Compensate instead of applying stale staged
                                    // content onto a newer committed profile revision.
                                    self.compensate(&prepared)?;
                                    return Ok((0, 0, 0, 1));
                                }
                                JournalLocation::FilePrepared if target_matches => {
                                    self.promote(&prepared)?;
                                    self.complete(&prepared)?;
                                    return Ok((0, 0, 1, 0));
                                }
                                JournalLocation::FilePromoting | JournalLocation::FilePromoted
                                    if target_matches =>
                                {

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Inspect the operation journal and the managed path; if the current content is valid, discard the stale journal (remove operation artifacts) so recovery no longer tries to apply it
  2. Re-run recovery after restoring the target to the expected pre-promote content (e.g. from the backup recorded in the journal) if stale application is acceptable
  3. Trigger compensation for the superseded operation instead of forward recovery
  4. Fix the writer that mutates the target without bumping the revision/creating its own journal to prevent future divergence

Example fix

// before: recovery crashes on diverged target
recovery.recover_stale_operations()?;
// after: drop superseded journals whose target diverged
for op in recovery.stale_operations() {
    if recovery.revision_advanced_past(&op) && !recovery.target_is_pre_promote(&op) {
        recovery.discard_journal(op.id)?;
    } else {
        recovery.recover(&op)?;
    }
}
Defensive patterns

Strategy: validation

Validate before calling

if recovery.revision_past(&op) && !recovery.target_pre_promote(&op)? {
    // superseded/diverged: discard journal instead of recovering
    recovery.discard_journal(&op.id)?;
} else {
    recovery.recover(&op)?;
}

Try / catch

match recovery.recover_stale_operations() {
    Err(e) if e.to_string().contains("diverged before recovery") => {
        // do not retry; inspect and drop the stale journal or compensate
        inspect_and_resolve_stale_journal()?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Running recovery (e.g. on startup after a crash) for an operation whose journal revision is older than the current revision, when target_is_pre_promote(&root, &operation_id, &target) returns false — i.e. the current content of the managed target is neither the staged/promoted content nor the recorded pre-promote content.

Common situations: The profile file was hand-edited or modified by another tool between the crash and recovery; a second transaction already applied and advanced the revision while the journal of the first was left behind; a partial promote/crash left mixed state on disk.

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/98364e2154681be5. Report an issue: GitHub.