libnyanpasu/clash-nyanpasu · error

materialization journal is not a regular file

Error message

materialization journal is not a regular file: {}

What it means

During profile materialization, `read_journal` loads a journal YAML file (named `<operation_id>.yaml`) from the materialization root's staging/backup/cleanup directory to verify and resume an operation. Before parsing it, the code uses `symlink_metadata` to check the entry is a plain regular file; if it is a symlink/reparse point or a directory (or other non-file entry), this error is thrown. This guards against tampered or corrupted private storage where a journal path has been replaced by something other than the expected file.

Solutions

  1. Delete the offending entry at the reported path so the materialization code can treat the operation as absent/redo it
  2. Check what the path is (ls -la / dir /R) to confirm it is a symlink or directory before deleting
  3. Exclude the app's private materialization root from symlink-creating dotfile managers and cloud sync tools
  4. If operations repeatedly fail, wipe the materialization staging/backup/cleanup directories (they are transient) and re-run the profile materialization

Example fix

// before: journal path replaced by a symlink
~/.local/share/nyanpasu/materialization/staging/journals/abc123.yaml -> /etc/passwd
// after: remove the non-regular entry and let the app recreate it
rm ~/.local/share/nyanpasu/materialization/staging/journals/abc123.yaml
Defensive patterns

Strategy: validation

Validate before calling

use std::fs::symlink_metadata;
fn journal_is_regular(path: &std::path::Path) -> bool {
    match symlink_metadata(path) {
        Ok(md) => md.is_file() && !md.file_type().is_symlink(),
        Err(_) => false,
    }
}

Type guard

fn is_regular_file_md(md: &std::fs::Metadata) -> bool {
    md.is_file() && !md.file_type().is_symlink()
}

Try / catch

match read_journal(&path, &op_id) {
    Err(e) if e.to_string().contains("not a regular file") => {
        let _ = std::fs::remove_file(&path); // drop tampered entry, re-materialize
    }
    Err(e) => return Err(e),
    Ok(j) => use_journal(j),
}

Prevention

When it happens

Trigger: `read_journal` is called with a journal path whose `symlink_metadata` reports a symlink, Windows reparse point, directory, FIFO, or other non-regular-file entry at `<root>/{staging,backup,cleanup}/.../<operation_id>.yaml`. E.g. a user or tool replaced the journal file with a symlink, or a directory was created where the journal should be.

Common situations: Manual tampering with the app's private materialization directory; restore/sync tools (dotfile managers, cloud sync) that convert files into symlinks; a crashed or buggy older version leaving a directory at the journal path; malware or an overzealous cleanup script.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

        }
        Ok(())
    }

    fn write_journal_new(path: &Path, journal: &MaterializationJournal) -> anyhow::Result<()> {
        let content =
            serde_yaml::to_string(journal).context("serialize materialization journal")?;
        AtomicFile::new(path, OverwriteBehavior::DisallowOverwrite)
            .write(|file| file.write_all(content.as_bytes()))
            .with_context(|| format!("write materialization journal {}", path.display()))?;
        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");

View on GitHub (pinned to f7dbce2997)