libnyanpasu/clash-nyanpasu · error

materialization journal operation id mismatch

Error message

materialization journal operation id mismatch

What it means

`read_journal` validates that the parsed `MaterializationJournal`'s `operation_id` equals the operation id used to compute the journal path, and that the id passes `valid_operation_id` (a well-formed identifier). A mismatch means the journal file at `<operation_id>.yaml` contains a different or malformed operation id, so it cannot be trusted as the record for this operation. The library refuses to use it to drive recovery.

Solutions

  1. Delete the mismatched journal file `<root>/<location>/<operation_id>.yaml` so the operation is treated as unknown and re-run materialization
  2. Do not rename or copy journal files; each journal is keyed by its operation id
  3. If migrating data, use the app's own export/import rather than copying the materialization directory
  4. Verify the YAML's `operation_id` field matches the file name if you must inspect it manually

Example fix

// before: file staging/journals/def456.yaml containing
// operation_id: abc123
mv staging/journals/def456.yaml /tmp/inspect/
// after: let the app recreate a journal for the current operation id
# remove mismatched journal, re-run profile materialization
Defensive patterns

Strategy: validation

Validate before calling

fn valid_op_id(id: &str) -> bool {
    !id.is_empty() && id.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
}
// before trusting a journal file, check name <-> content correspondence:
// file_name stem must equal journal.operation_id and valid_op_id must hold

Type guard

fn journal_matches(j: &MaterializationJournal, op_id: &str) -> bool {
    j.operation_id == op_id && valid_op_id(&j.operation_id)
}

Try / catch

match read_journal(&path, &op_id) {
    Err(e) if e.to_string().contains("operation id mismatch") => {
        let _ = std::fs::remove_file(&path); // stale/tampered journal; re-run operation
    }
    Err(e) => return Err(e),
    Ok(j) => use_journal(j),
}

Prevention

When it happens

Trigger: Calling recovery/commit code paths that call `read_journal(path, operation_id)` where the YAML on disk stores a different `operation_id`, an empty id, or one failing `valid_operation_id` (e.g. containing path separators, `..`, or invalid characters). Typically caused by copying/renaming journal files by hand or leftover journals from a different operation.

Common situations: Manually copying journal YAML files between operation directories; restoring a partial backup of the materialization root where file names and contents no longer correspond; hand-edited journals; stale journals from an aborted upgrade.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

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

        set_private_file_permissions(path)?;
        sync_directory(path.parent().expect("journal has parent"))
    }

    fn read_journal(path: &Path, operation_id: &str) -> anyhow::Result<MaterializationJournal> {
        let metadata = std::fs::symlink_metadata(path)
            .with_context(|| format!("inspect materialization journal {}", path.display()))?;
        if is_symlink_or_reparse(&metadata) || !metadata.is_file() {
            bail!(
                "materialization journal is not a regular file: {}",
                path.display()
            );
        }
        let content = std::fs::read_to_string(path)
            .with_context(|| format!("read materialization journal {}", path.display()))?;
        let journal: MaterializationJournal = serde_yaml::from_str(&content)
            .with_context(|| format!("parse materialization journal {}", path.display()))?;
        if journal.operation_id != operation_id || !valid_operation_id(&journal.operation_id) {
            bail!("materialization journal operation id mismatch");
        }
        if journal
            .managed_path
            .as_path()
            .components()
            .any(|component| is_materialization_root_name(component.as_os_str()))
        {
            bail!("materialization journal targets reserved private storage");
        }
        if journal.hash.len() != 64 || !journal.hash.bytes().all(|byte| byte.is_ascii_hexdigit()) {
            bail!("materialization journal hash is invalid");
        }
        Ok(journal)
    }

    fn remove_nofollow(path: &Path) -> anyhow::Result<()> {
        match std::fs::symlink_metadata(path) {
            Ok(metadata) if metadata.is_dir() && !is_symlink_or_reparse(&metadata) => {

View on GitHub (pinned to f7dbce2997)