libnyanpasu/clash-nyanpasu · error

pending cleanup journal is not a regular file

Error message

pending cleanup journal is not a regular file

What it means

While locating a cleanup journal, locate_cleanup inspects the Pending phase path with symlink_metadata. If the path exists but is anything other than a plain regular file (symlink, reparse point, directory), it bails: recovery code must not read through or delete foreign filesystem objects. A journal that exists but is not a regular file means external interference in the private journal directory.

Source

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

                    .map(|source| source.materialized().file.clone())
            })
            .collect()
    }

    fn locate_cleanup(
        root: &Path,
        operation_id: &str,
    ) -> anyhow::Result<Option<(CleanupPhase, MaterializationJournal)>> {
        if !valid_operation_id(operation_id) {
            bail!("invalid profile cleanup operation id");
        }
        let pending_path = Self::cleanup_path(root, CleanupPhase::Pending, operation_id);
        let ready_path = Self::cleanup_path(root, CleanupPhase::Ready, operation_id);
        let pending = match std::fs::symlink_metadata(&pending_path) {
            Ok(metadata) if !is_symlink_or_reparse(&metadata) && metadata.is_file() => {
                Some(Self::read_journal(&pending_path, operation_id)?)
            }
            Ok(_) => bail!("pending cleanup journal is not a regular file"),
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
            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)))

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Inspect the reported Pending journal path, remove the non-file artifact, and rerun recovery (a fresh attempt can then be started or the previous one abandoned).
  2. Exclude the private journal directory from cloud sync, backup, and antivirus scanning.
  3. If the profiles root may be writable by other users, move it to a user-private location to rule out symlink planting.
  4. Abandon the operation by clearing all journals/tombstones for that operation_id and re-plan the cleanup.

Example fix

// before: recovery keeps failing on the bogus artifact
let state = locate_cleanup(root, &id)?;
// after: clear the non-regular Pending artifact first
let p = cleanup_path(root, CleanupPhase::Pending, &id);
let meta = std::fs::symlink_metadata(&p)?;
if meta.is_symlink() || !meta.is_file() {
    std::fs::remove_file(&p)?; // or remove_dir_all if a directory
}
let state = locate_cleanup(root, &id)?;
Defensive patterns

Strategy: validation

Validate before calling

fn pending_journal_clean(root: &Path, id: &str) -> Result<bool, std::io::Error> {
    match std::fs::symlink_metadata(cleanup_path(root, CleanupPhase::Pending, id)) {
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(true),
        Err(e) => Err(e),
        Ok(m) => Ok(!m.is_symlink() && m.is_file()),
    }
}

Type guard

fn is_plain_file(m: &std::fs::Metadata) -> bool {
    !m.is_symlink() && m.is_file()
}

Try / catch

match locate_cleanup(root, id) {
    Err(e) if e.to_string().contains("pending cleanup journal is not a regular file") => {
        // remove the artifact, then either retry recovery or abandon the operation
    }
    other => other,
}

Prevention

When it happens

Trigger: Running cleanup lookup/recovery for an operation whose Pending cleanup journal path exists as a symlink, Windows reparse point, or directory — caused by cloud-sync placeholders, quarantine stubs, or manual tampering with the journal directory.

Common situations: OneDrive/Dropbox converting stale journal files to on-demand placeholders on Windows; antivirus replacing the file after quarantine; a developer creating a directory at that path while debugging; symlink attacks if the profiles root is on a shared/writable location.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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