astrid-runtime/astrid · error
private file has links; durable media must have exactly one
Error message
private file has {} links; durable media must have exactly one What it means
The library requires private files to have exactly one hard link (st_nlink == 1) because durable security-sensitive media must not be reachable through a second directory entry, which would let edits bypass the enforced permissions. It throws this when validation counts more than one link.
Solutions
- Remove the extra hard links (`ls -li <path>` to find inodes, then delete the other directory entries) until the link count is 1.
- Copy the file to a fresh inode instead of hard-linking: `cp <src> <dst>` then `rm <src>` and `mv <dst> <src>`, keeping mode 0600.
- Disable hard-link-based backup/dedup for the private directory and re-create the file via the library.
- Recreate the file with atomic_write_private_file, which writes a fresh single-link inode.
Example fix
// before (nlink=2 due to backup hard link)
validate_private_file(Path::new("/home/me/.astrid/credentials"))?;
// after (replace with fresh single-link file)
// $ cp /home/me/.astrid/credentials /tmp/cred && chmod 600 /tmp/cred
// $ rm /home/me/.astrid/credentials && mv /tmp/cred /home/me/.astrid/credentials
validate_private_file(Path::new("/home/me/.astrid/credentials"))?; Defensive patterns
Strategy: validation
Validate before calling
use std::os::unix::fs::MetadataExt;
fn has_single_link(path: &std::path::Path) -> bool {
std::fs::metadata(path).map(|m| m.nlink() == 1).unwrap_or(false)
} Type guard
fn is_unlinked_alias_free(path: &std::path::Path) -> bool {
std::fs::metadata(path).map(|m| m.nlink() == 1).unwrap_or(false)
} Try / catch
match validate_private_file(path) {
Err(e) if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains("links") => {
// replace with a fresh single-link copy
},
other => other?,
} Prevention
- Never `ln` (hard link) private files elsewhere
- Exclude private directories from hard-link-based backups (rsync --link-dest, rsnapshot)
- Prefer `cp` over hard links when duplicating sensitive files
When it happens
Trigger: validate_private_file finds st_nlink > 1: someone hard-linked the file elsewhere (`ln`), or a backup/dedup tool created additional links to the inode.
Common situations: Backup tools using hard links (e.g. rsync --link-dest, rsnapshot); developer manually hard-linking the credential file into another project; deduplicating filesystems linking shared inodes.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Astrid durable media is redirected or not a regular file
- cache changed to a redirect or special entry
- capsule materialization target is redirected or not a…
- capsule projection contains a symbolic link
- capsule projection escaped its root
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/b2a814b93e9ce040.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-core/src/platform_fs.rs:563
));
}
if metadata.st_uid != nix::unistd::getuid().as_raw() {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"private file is not owned by the current user: {}",
path.display()
),
));
}
if metadata.st_mode & 0o777 != 0o600 {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!("private file is not owner-only: {}", path.display()),
));
}
if metadata.st_nlink != 1 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"private file has {} links; durable media must have exactly one",
metadata.st_nlink
),
));
}
validate_no_extended_acl(path)?;
Ok(())
}
#[cfg(target_os = "macos")]
fn remove_extended_acl_macos(path: &Path) -> io::Result<()> {
let path = absolute_command_path(path)?;
let status = std::process::Command::new("/bin/chmod")
.arg("-N")
.arg(path)
.status()?;View on GitHub (pinned to affd8760f4)