{"record":{"id":"8a553e0b2367ff5a","repo":"libnyanpasu/clash-nyanpasu","slug":"materialization-journal-is-not-a-regular-file","errorCode":null,"errorMessage":"materialization journal is not a regular file: {}","messagePattern":"materialization journal is not a regular file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":612,"sourceCode":"        }\n        Ok(())\n    }\n\n    fn write_journal_new(path: &Path, journal: &MaterializationJournal) -> anyhow::Result<()> {\n        let content =\n            serde_yaml::to_string(journal).context(\"serialize materialization journal\")?;\n        AtomicFile::new(path, OverwriteBehavior::DisallowOverwrite)\n            .write(|file| file.write_all(content.as_bytes()))\n            .with_context(|| format!(\"write materialization journal {}\", path.display()))?;\n        set_private_file_permissions(path)?;\n        sync_directory(path.parent().expect(\"journal has parent\"))\n    }\n\n    fn read_journal(path: &Path, operation_id: &str) -> anyhow::Result<MaterializationJournal> {\n        let metadata = std::fs::symlink_metadata(path)\n            .with_context(|| format!(\"inspect materialization journal {}\", path.display()))?;\n        if is_symlink_or_reparse(&metadata) || !metadata.is_file() {\n            bail!(\n                \"materialization journal is not a regular file: {}\",\n                path.display()\n            );\n        }\n        let content = std::fs::read_to_string(path)\n            .with_context(|| format!(\"read materialization journal {}\", path.display()))?;\n        let journal: MaterializationJournal = serde_yaml::from_str(&content)\n            .with_context(|| format!(\"parse materialization journal {}\", path.display()))?;\n        if journal.operation_id != operation_id || !valid_operation_id(&journal.operation_id) {\n            bail!(\"materialization journal operation id mismatch\");\n        }\n        if journal\n            .managed_path\n            .as_path()\n            .components()\n            .any(|component| is_materialization_root_name(component.as_os_str()))\n        {\n            bail!(\"materialization journal targets reserved private storage\");","sourceCodeStart":594,"sourceCodeEnd":630,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L594-L630","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","solutions":["Delete the offending entry at the reported path so the materialization code can treat the operation as absent/redo it","Check what the path is (ls -la / dir /R) to confirm it is a symlink or directory before deleting","Exclude the app's private materialization root from symlink-creating dotfile managers and cloud sync tools","If operations repeatedly fail, wipe the materialization staging/backup/cleanup directories (they are transient) and re-run the profile materialization"],"exampleFix":"// before: journal path replaced by a symlink\n~/.local/share/nyanpasu/materialization/staging/journals/abc123.yaml -> /etc/passwd\n// after: remove the non-regular entry and let the app recreate it\nrm ~/.local/share/nyanpasu/materialization/staging/journals/abc123.yaml","handlingStrategy":"validation","validationCode":"use std::fs::symlink_metadata;\nfn journal_is_regular(path: &std::path::Path) -> bool {\n    match symlink_metadata(path) {\n        Ok(md) => md.is_file() && !md.file_type().is_symlink(),\n        Err(_) => false,\n    }\n}","typeGuard":"fn is_regular_file_md(md: &std::fs::Metadata) -> bool {\n    md.is_file() && !md.file_type().is_symlink()\n}","tryCatchPattern":"match read_journal(&path, &op_id) {\n    Err(e) if e.to_string().contains(\"not a regular file\") => {\n        let _ = std::fs::remove_file(&path); // drop tampered entry, re-materialize\n    }\n    Err(e) => return Err(e),\n    Ok(j) => use_journal(j),\n}","preventionTips":["Never symlink files inside the app's private materialization root","Exclude the materialization directory from cloud-sync and dotfile managers","Do not create directories at journal paths","Treat the materialization root as app-owned; recreate it rather than repairing by hand"],"tags":["filesystem","security","symlink"],"backgroundTag":"invalid-argument-value","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}