astrid-runtime/astrid · error

scan {}: {error}

Error message

scan {}: {error}

What it means

Thrown by admit_unbound_legacy_principal_homes when fs::read_dir on the legacy home root fails. The OS error kind is preserved (e.g. PermissionDenied, NotFound) and the scanned path is prefixed for context. This wraps the OS error so callers know which directory could not be enumerated during the unbound-leftover scan.

Source

Thrown at crates/astrid-kernel/src/principal_home_migration/unbound.rs:51

        Ok(metadata) => metadata,
        Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),
        Err(error) => return Err(error),
    };
    if metadata.file_type().is_symlink() || !metadata.is_dir() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!(
                "legacy principal home root is not a regular directory: {}",
                source_root.display()
            ),
        ));
    }
    astrid_core::platform_fs::ensure_private_directory_tree(&source_root)?;
    astrid_core::platform_fs::verify_no_redirects(&source_root)?;

    let mut entries = Vec::new();
    for entry in fs::read_dir(&source_root).map_err(|error| {
        io::Error::new(
            error.kind(),
            format!("scan {}: {error}", source_root.display()),
        )
    })? {
        entries.push(entry?);
    }
    for entry in entries {
        admit_or_quarantine_entry(home, directory, identity, entry.path(), &entry.file_name())
            .await?;
    }
    Ok(())
}

async fn admit_or_quarantine_entry(
    home: &AstridHome,
    directory: &PrincipalDirectory,
    identity: &dyn IdentityStore,
    path: PathBuf,

View on GitHub (pinned to affd8760f4)

Solutions

  1. Fix permissions so the process can read the legacy home root (chown/chmod, restore ACLs).
  2. Confirm the path exists on a mounted, healthy filesystem (df/mount, remount if needed).
  3. Re-run the migration after any concurrent writers/deleters finish.
  4. Run the migration as a user with access to the home tree rather than fighting ownership as root.

Example fix

// before
$ ls ~/.legacy-homes
ls: cannot open directory: Permission denied
// after
$ chmod 700 ~/.legacy-homes && chown $USER ~/.legacy-homes
Defensive patterns

Strategy: try-catch

Validate before calling

fn source_root_readable(path: &Path) -> bool {
    fs::read_dir(path).map(|_| true).unwrap_or(false)
}

Try / catch

match admit_unbound_legacy_principal_homes(&source_root, ...) {
    Ok(()) => (),
    Err(e) => {
        eprintln!("scan failed for {}: {e}", source_root.display());
        if e.kind() == io::ErrorKind::PermissionDenied {
            // fix permissions, then retry once
            fix_home_permissions(&source_root)?;
            admit_unbound_legacy_principal_homes(&source_root, ...)?;
        } else { return Err(e); }
    }
}

Prevention

When it happens

Trigger: admit_unbound_legacy_principal_homes calls read_dir on a source_root that exists and passed the private-directory-tree and no-redirects checks, but the OS cannot open it for reading — permissions changed between checks, the path was removed concurrently, or it sits on a failing mount.

Common situations: Read permission removed on the home directory (chmod/ACL/ownership change); the directory deleted by another process mid-scan; the path on an unmounted or disconnected filesystem.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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