{"record":{"id":"5b23558073411813","repo":"libnyanpasu/clash-nyanpasu","slug":"runtime-candidate-path-is-not-a-private-directory","errorCode":null,"errorMessage":"runtime candidate path is not a private directory: {path}","messagePattern":"runtime candidate path is not a private directory: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/client/runtime.rs","lineNumber":275,"sourceCode":"\nimpl Drop for CandidateFile {\n    fn drop(&mut self) {\n        if !self.cleaned {\n            let _ = std::fs::remove_file(&self.path);\n        }\n    }\n}\n\nasync fn prepare_private_dir(path: &Utf8Path) -> anyhow::Result<()> {\n    if let Ok(metadata) = tokio::fs::symlink_metadata(path).await\n        && is_symlink_or_reparse(&metadata)\n    {\n        anyhow::bail!(\"runtime candidate directory is a symlink or reparse point: {path}\");\n    }\n    tokio::fs::create_dir_all(path).await?;\n    let metadata = tokio::fs::symlink_metadata(path).await?;\n    if is_symlink_or_reparse(&metadata) || !metadata.is_dir() {\n        anyhow::bail!(\"runtime candidate path is not a private directory: {path}\");\n    }\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::PermissionsExt;\n        tokio::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700)).await?;\n    }\n    Ok(())\n}\n\n#[cfg(unix)]\nfn is_symlink_or_reparse(metadata: &std::fs::Metadata) -> bool {\n    metadata.file_type().is_symlink()\n}\n\n#[cfg(windows)]\nfn is_symlink_or_reparse(metadata: &std::fs::Metadata) -> bool {\n    use std::os::windows::fs::MetadataExt;\n    const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x400;","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/client/runtime.rs#L257-L293","documentation":"Raised by prepare_private_dir (backend/tauri/src/client/runtime.rs:275) after create_dir_all when the final symlink_metadata shows the path is either still a symlink/reparse point or is not a directory. The runtime store requires a real, private directory to hold exclusive-create candidate files; anything else is rejected to protect file privacy guarantees (0600 files in a 0700 dir).","triggerScenarios":"create_candidate_with_names, cleanup_stale_candidates, or candidate_collision_retries_with_exclusive_create calls prepare_private_dir; after create_dir_all, the path exists but is a regular file, a symlink, or another non-directory node, or was swapped between the create and the metadata check.","commonSituations":"A file named like the candidate dir exists at that path so create_dir_all fails silently or the check runs against the file; a race where another process replaced the directory with a symlink; corrupted install where the data dir path collides with a file.","solutions":["Check what exists at the path (ls -la / dir) and delete the offending file or symlink so a real directory can be created.","Re-run after removing the file at that path; create_dir_all will then create a proper directory.","Resolve the process racing on this path (another app instance or sync tool) before retrying.","Verify app data directory configuration points at a directory, not a file path."],"exampleFix":"// before: a regular file occupies the candidate dir path\n// after: remove the file and create the directory\nstd::fs::remove_file(\"/app/data/candidates\")?;\nstd::fs::create_dir_all(\"/app/data/candidates\")?;","handlingStrategy":"validation","validationCode":"let md = std::fs::symlink_metadata(&candidate_path)?;\nif !md.is_dir() || md.file_type().is_symlink() {\n    // clear the way so create_dir_all can build a real directory\n    std::fs::remove_file(&candidate_path)?;\n    std::fs::create_dir_all(&candidate_path)?;\n}","typeGuard":"fn is_real_directory(path: &Utf8Path) -> bool {\n    std::fs::symlink_metadata(path.as_std_path())\n        .map(|md| md.is_dir() && !md.file_type().is_symlink())\n        .unwrap_or(false)\n}","tryCatchPattern":"match store.create_candidate(&bytes).await {\n    Ok(c) => c,\n    Err(e) if e.to_string().contains(\"not a private directory\") => {\n        std::fs::remove_file(path.as_std_path()).ok();\n        std::fs::create_dir_all(path.as_std_path())?;\n        store.create_candidate(&bytes).await?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Ensure no file or symlink occupies the configured candidate dir path at startup","Run a single app instance or lock the data dir to avoid races replacing the directory","Validate data-dir layout during bootstrap, before any writes","Keep the path configuration pointing at a directory, never a file"],"tags":["filesystem","directory","rust"],"backgroundTag":"path-is-not-a-directory","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"}