astrid-runtime/astrid · error · io::Error

InvalidInput

InvalidInput

Error message

private Windows file has no parent directory

What it means

read_private_file_to_string validates the path as a local absolute path and then needs the parent directory to capture and verify a TrustedPathGuard with ExactPrivateDirectory contract. If Path::parent() returns None (a componentless/root path), the directory boundary cannot be established and the read fails with InvalidInput.

Source

Thrown at crates/astrid-core/src/platform_fs/windows/private_file.rs:20

use super::error::with_context;
use super::executable::{acquire_private_file_transaction_lock, recovery_error};
use super::io::{
    FileContract, PreparationCleanup, flush_guarded_open_file, guarded_file_exists,
    hash_guarded_regular_file, move_guarded_file, open_guarded_regular_file,
    read_guarded_regular_file, remove_guarded_file, replace_file_checked, stage_transaction_copy,
    stage_transaction_copy_authenticated, stage_unique_bytes, stage_unique_bytes_retained,
    validate_file_contract,
};
use super::path::{
    BoundaryContract, TrustedPathGuard, file_identity, validate_local_absolute_path,
};
use super::prelude::*;

pub(in crate::platform_fs) fn read_private_file_to_string(path: &Path) -> io::Result<String> {
    validate_local_absolute_path(path)?;
    let parent = path.parent().ok_or_else(|| {
        io::Error::new(
            io::ErrorKind::InvalidInput,
            "private Windows file has no parent directory",
        )
    })?;
    let guard = TrustedPathGuard::capture(parent)?;
    guard.verify_contract(BoundaryContract::ExactPrivateDirectory)?;
    let _transaction_lock = acquire_private_file_transaction_lock(parent, &guard)?;
    recover_private_file_transaction_locked(parent, &guard)?;
    guard.verify_contract(BoundaryContract::ExactPrivateDirectory)?;

    let mut file = open_guarded_regular_file(&guard, path, FileContract::ExactPrivate)?;
    let identity = file_identity(&file)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    validate_file_contract(
        file.as_raw_handle().cast(),
        path,
        FileContract::ExactPrivate,

View on GitHub (pinned to affd8760f4)

Solutions

  1. Pass the complete absolute file path including its directory (e.g. `C:\ProgramData\Astrid\secret.key`).
  2. Fix the code that builds the path so the directory component is not dropped (avoid Path::file_name-only round trips).
  3. Check path.parent().is_some() before calling, and fail with a clearer application-level error.

Example fix

// before
let path = Path::new("C:\\ProgramData\\Astrid").file_name(); // wrong shape
read_private_file_to_string(&dir_only_path)?;
// after
let path = Path::new("C:\\ProgramData\\Astrid\\secret.key");
read_private_file_to_string(path)?;
Defensive patterns

Strategy: validation

Validate before calling

fn readable_private(p: &Path) -> bool {
    is_local_disk_path(p) && p.parent().is_some() && p.file_name().is_some()
}

Try / catch

match read_private_file_to_string(path) {
    Err(e) if e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains("no parent directory") => {
        // config/path-builder bug: full path with directory required
    },
    other => other?,
}

Prevention

When it happens

Trigger: Calling read_private_file_to_string with a path whose parent() is None — e.g. a bare root like `C:\`, a path built from only a prefix component, or a path stripped of its directory portion.

Common situations: Accidentally passing the config key or file name alone instead of the full path; a config value that lost its directory during templating; treating a directory path as a file path.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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