astrid-runtime/astrid · error

private path has an extended access-control list

Error message

private path has an extended access-control list

What it means

On macOS, private paths must not carry extended ACL entries beyond the standard POSIX mode bits. validate_no_extended_acl_macos parses `ls -lde` output and throws this when any numbered ACL entry is present, since extra ACL grants can silently widen access even when the mode is 0600.

Solutions

  1. Strip ACLs from the file/directory: `chmod -N <path>` (and `chmod -RN <dir>` recursively).
  2. Recreate the file/directory through the library, which runs chmod -N automatically on restrict/ensure paths.
  3. Move the private directory out of a parent folder with inherited ACLs (e.g. ~/Library CloudStorage) to a plain home path.
  4. Inspect with `ls -lde <path>` to confirm no numbered ACL entries remain before retrying.

Example fix

// before (macOS, file has inherited ACL entries)
validate_private_file(Path::new("/Users/me/.astrid/credentials"))?;
// after
// $ chmod -N /Users/me/.astrid/credentials
validate_private_file(Path::new("/Users/me/.astrid/credentials"))?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_no_macos_acl(path: &std::path::Path) -> std::io::Result<bool> {
    let out = std::process::Command::new("/bin/ls")
        .args(["-lde"]).arg(path).env("LC_ALL", "C").output()?;
    let text = String::from_utf8_lossy(&out.stdout);
    Ok(!text.lines().skip(1).any(|l| l.trim_start().split_once(':')
        .is_some_and(|(i, _)| i.parse::<usize>().is_ok())))
}

Try / catch

match validate_private_file(path) {
    Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied
        && e.to_string().contains("access-control list") => {
        let _ = std::process::Command::new("/bin/chmod").args(["-N"]).arg(path).status()?;
    },
    other => other?,
}

Prevention

When it happens

Trigger: validate_private_directory or validate_private_file is called on macOS and the target has ACL entries (visible as lines like ` 0: group:everyone deny delete` in `ls -lde`).

Common situations: Files restored from backups or Finder copies that carried ACLs; files created under a directory with inherited ACLs; files synced from cloud-drive folders that attach quarantine/ACL entries.

Understand the failure class

Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/8611fcb29c42f365. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-core/src/platform_fs.rs:612

    let output = std::process::Command::new("/bin/ls")
        .arg("-lde")
        .arg(path)
        .env("LC_ALL", "C")
        .output()?;
    if !output.status.success() {
        return Err(io::Error::other(
            "failed to inspect extended access-control list",
        ));
    }
    let listing = String::from_utf8(output.stdout)
        .map_err(|_| io::Error::other("access-control listing is not UTF-8"))?;
    let has_acl_entry = listing.lines().skip(1).any(|line| {
        line.trim_start()
            .split_once(':')
            .is_some_and(|(index, _)| index.parse::<usize>().is_ok())
    });
    if has_acl_entry {
        Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "private path has an extended access-control list",
        ))
    } else {
        Ok(())
    }
}

#[cfg(target_os = "macos")]
fn absolute_command_path(path: &Path) -> io::Result<PathBuf> {
    if path.is_absolute() {
        Ok(path.to_path_buf())
    } else {
        Ok(std::env::current_dir()?.join(path))
    }
}

#[cfg(unix)]

View on GitHub (pinned to affd8760f4)