libnyanpasu/clash-nyanpasu · error

cleanup journals have conflicting payloads

Error message

cleanup journals have conflicting payloads

What it means

When locate_cleanup finds journals in both the Pending and Ready phase slots for the same operation_id, it requires their payloads to be identical (a crash between the Windows write-through and source deletion can legitimately leave both). If the two journals disagree, the library bails rather than choosing a phase, because advancing cleanup with the wrong journal could delete the wrong profile files.

Source

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

            Err(error) => return Err(error).context("inspect pending cleanup journal"),
        };
        let ready = match std::fs::symlink_metadata(&ready_path) {
            Ok(metadata) if !is_symlink_or_reparse(&metadata) && metadata.is_file() => {
                Some(Self::read_journal(&ready_path, operation_id)?)
            }
            Ok(_) => bail!("ready cleanup journal is not a regular file"),
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
            Err(error) => return Err(error).context("inspect ready cleanup journal"),
        };
        match (pending, ready) {
            (None, None) => Ok(None),
            (Some(journal), None) => Ok(Some((CleanupPhase::Pending, journal))),
            (None, Some(journal)) => Ok(Some((CleanupPhase::Ready, journal))),
            (Some(pending), Some(ready)) if pending == ready => {
                Self::remove_private_regular(&pending_path)?;
                Ok(Some((CleanupPhase::Ready, ready)))
            }
            (Some(_), Some(_)) => bail!("cleanup journals have conflicting payloads"),
        }
    }

    fn has_materialization_journal(root: &Path, operation_id: &str) -> bool {
        JournalLocation::ALL.iter().any(|location| {
            std::fs::symlink_metadata(Self::journal_path(root, *location, operation_id)).is_ok()
        })
    }

    fn has_cleanup_journal(root: &Path, operation_id: &str) -> bool {
        [CleanupPhase::Pending, CleanupPhase::Ready]
            .iter()
            .any(|phase| {
                std::fs::symlink_metadata(Self::cleanup_path(root, *phase, operation_id)).is_ok()
            })
    }

    fn remove_cleanup_tombstone(root: &Path, operation_id: &str) -> anyhow::Result<()> {

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Discard the whole cleanup operation: remove both Pending and Ready journals for that operation_id and restart the cleanup with a new operation id.
  2. Only if you can prove which payload is current (timestamps, transaction log), keep the matching pair by deleting the stale one and retry.
  3. Never reuse operation ids across cleanup attempts.
  4. Investigate disk health / crash frequency if divergent journals appear regularly.

Example fix

// before: recovery cannot disambiguate
let state = locate_cleanup(root, &id)?;
// after: abandon the ambiguous cleanup entirely
std::fs::remove_file(cleanup_path(root, CleanupPhase::Pending, &id))?;
std::fs::remove_file(cleanup_path(root, CleanupPhase::Ready, &id))?;
start_cleanup(new_plan)?; // fresh operation_id
Defensive patterns

Strategy: try-catch

Validate before calling

fn cleanup_pair_agrees(root: &Path, id: &str) -> bool {
    match (std::fs::read(cleanup_path(root, CleanupPhase::Pending, id)),
           std::fs::read(cleanup_path(root, CleanupPhase::Ready, id))) {
        (Ok(a), Ok(b)) => a == b,
        _ => true,
    }
}

Try / catch

match locate_cleanup(root, id) {
    Err(e) if e.to_string().contains("conflicting payloads") => {
        // ambiguous cleanup: delete both journals and start a new cleanup attempt
    }
    other => other,
}

Prevention

When it happens

Trigger: Recovery after a crash where both cleanup phases exist but were written by different attempts sharing an operation_id, or one journal was corrupted/torn so its payload no longer matches its sibling; mixing journals restored from backups of different runs.

Common situations: Storage faults or forced power-off mid transition producing divergent copies; an operation_id reused across cleanup attempts; manual copying of a single phase journal during debugging, leaving a stale sibling behind.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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