{"record":{"id":"2905f0d9b1fc47ca","repo":"astrid-runtime/astrid","slug":"private-atomic-file-has-no-parent","errorCode":null,"errorMessage":"private atomic file has no parent: {}","messagePattern":"private atomic file has no parent: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/platform_fs.rs","lineNumber":660,"sourceCode":"        io::Error::new(\n            io::ErrorKind::InvalidInput,\n            format!(\"private file has no name: {}\", path.display()),\n        )\n    })?;\n    let directory = open_directory_no_follow_unix(parent)?;\n    let flags = OFlag::O_RDONLY | OFlag::O_NOFOLLOW | OFlag::O_CLOEXEC;\n    openat(&directory, name, flags, Mode::from_bits_truncate(0o600))\n        .map(std::fs::File::from)\n        .map_err(nix_io_error)\n}\n\n#[cfg(unix)]\nfn atomic_write_private_file_unix(path: &Path, bytes: &[u8]) -> io::Result<()> {\n    use std::io::Write as _;\n    use std::os::unix::fs::OpenOptionsExt as _;\n\n    let parent = path.parent().ok_or_else(|| {\n        io::Error::new(\n            io::ErrorKind::InvalidInput,\n            format!(\"private atomic file has no parent: {}\", path.display()),\n        )\n    })?;\n    ensure_private_directory(parent)?;\n    let temporary = parent.join(format!(\".astrid-private-{}\", uuid::Uuid::new_v4().simple()));\n    let write = (|| {\n        let mut file = std::fs::OpenOptions::new()\n            .write(true)\n            .create_new(true)\n            .mode(0o600)\n            .open(&temporary)?;\n        file.write_all(bytes)?;\n        file.sync_all()\n    })();\n    if let Err(error) = write {\n        let _ = std::fs::remove_file(&temporary);\n        return Err(error);","sourceCodeStart":642,"sourceCodeEnd":678,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/platform_fs.rs#L642-L678","documentation":"atomic_write_private_file_unix writes a private file by creating a 0600 temporary in the target's parent directory and renaming it into place. It throws this when path.parent() returns None (root/empty path), since there is no parent directory in which to stage the temporary file.","triggerScenarios":"Calling atomic_write_private_file with a path such as \"/\" or a path with no parent component, before it attempts ensure_private_directory on the parent.","commonSituations":"Misconfigured output path holding a mount root; path-building code that produced an empty/root path; forgetting to join the file name to the directory.","solutions":["Pass a full path including parent directory and file name, not \"/\".","Fix the configuration or variable supplying the output path.","Join the directory and file explicitly: parent.join(\"filename\").","Validate with `path.parent().is_some() && path.file_name().is_some()` before calling."],"exampleFix":"// before\natomic_write_private_file(Path::new(\"/\"), b\"data\")?;\n// after\natomic_write_private_file(&Path::new(\"/home/me/.astrid\").join(\"credentials\"), b\"data\")?;","handlingStrategy":"validation","validationCode":"fn is_writable_atomic_target(path: &std::path::Path) -> bool {\n    path.parent().is_some() && path.file_name().is_some()\n}","typeGuard":null,"tryCatchPattern":"match atomic_write_private_file(path, bytes) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => eprintln!(\"bad target path: {e}\"),\n    other => other?,\n}","preventionTips":["Always join directory and file name for atomic writes","Reject root/empty paths in configuration","Ensure the parent directory exists and is private before writing"],"tags":["filesystem","atomic-write","path","invalid-argument"],"backgroundTag":"invalid-argument-value","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}