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
- 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
- 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
- Trigger compensation for the superseded operation instead of forward recovery
- 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
- Never edit managed target files outside the transaction framework
- Ensure every writer bumps the revision and creates its own journal
- Run recovery promptly after a crash, before other tools touch the profile directory
- Compensate superseded operations instead of leaving stale journals behind
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
- materialization operation has conflicting journal payloads i
- invalid profile cleanup operation id
- pending cleanup journal is not a regular file
- ready cleanup journal is not a regular file
- compensation fenced by diverged target at {}
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/98364e2154681be5.
Report an issue: GitHub.