astrid-runtime/astrid · warning

InvalidInput

InvalidInput

Error message

Windows file name is too long

What it means

rename_guarded_file performs an NT-level rename (NtSetInformationFile with FILE_RENAME_INFORMATION) and must pack the destination file name as UTF-16 into a u32 length field. This error fires when the UTF-16 name length (UTF-16 code units × 2 bytes) cannot be represented as u32 — i.e. the destination path component is absurdly long. It is a defensive pre-flight check before allocating and filling the rename buffer.

Solutions

  1. Audit the code that constructs the destination path — a name this long means the path is being built incorrectly (check for repeated join/format loops).
  2. Validate the destination file name length before calling replace_file_checked/move_guarded_file (keep components under 255 UTF-16 units per Windows limits).
  3. Reject or sanitize untrusted input that feeds the destination file name before invoking the guarded move/replace APIs.

Example fix

// before: blindly passing a possibly runaway name
move_guarded_file(&guard, &source, &install_dir.join(&user_supplied_name))?;
// after: pre-validate the component
let name = user_supplied_name;
if name.chars().count() == 0 || name.chars().count() > 255 {
    return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid file name"));
}
move_guarded_file(&guard, &source, &install_dir.join(name))?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust: validate the destination component before any guarded rename/move
fn valid_component(name: &str) -> io::Result<()> {
    let units = name.encode_utf16().count();
    if units == 0 || units > 255 {
        return Err(io::Error::new(io::ErrorKind::InvalidInput, "file name component out of range"));
    }
    if name.contains(['/', '\\', ':', '*', '?', '"', '<', '>', '|']) {
        return Err(io::Error::new(io::ErrorKind::InvalidInput, "illegal characters in file name"));
    }
    Ok(())
}

Type guard

fn is_valid_windows_component(name: &str) -> bool {
    let units = name.encode_utf16().count();
    units > 0 && units <= 255 && !name.contains(['/', '\\', ':', '*', '?', '"', '<', '>', '|'])
}

Try / catch

// Rust
match replace_file_checked(&guard, &live, &replacement) {
    Ok(()) => finish(),
    Err(e) if e.kind() == io::ErrorKind::InvalidInput => {
        eprintln!("bad destination path for rename: {e}");
        // do not retry: fix the path construction
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Raised in rename_guarded_file (via replace_file_checked / move_guarded_file) when `destination_wide.len() * size_of::<u16>()` overflows u32::try_from. Only reachable with a destination name of more than ~2^31 UTF-16 code units — practically only via corrupted path inputs, unbounded string concatenation, or a bug that passes garbage into `destination`.

Common situations: A bug in caller code building destination paths in a loop (e.g. joining the same component repeatedly); deserialized/path-traversal-ish input containing a runaway file name; unit/integration tests passing malformed paths.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-core/src/platform_fs/windows/io.rs:511

    guard.verify_contract(boundary_contract)
}

fn rename_guarded_file(
    guard: &TrustedPathGuard,
    source: &Path,
    destination: &Path,
    replace: bool,
) -> io::Result<()> {
    let source_name = guarded_child_name(guard, source)?;
    let destination_name = guarded_child_name(guard, destination)?;
    let source = open_guarded_child(guard, source_name, DELETE | FILE_READ_ATTRIBUTES)?;
    let destination_wide = destination_name.encode_wide().collect::<Vec<_>>();
    let name_bytes = destination_wide
        .len()
        .checked_mul(size_of::<u16>())
        .and_then(|length| u32::try_from(length).ok())
        .ok_or_else(|| {
            io::Error::new(io::ErrorKind::InvalidInput, "Windows file name is too long")
        })?;
    let buffer_bytes = size_of::<FILE_RENAME_INFORMATION>()
        .checked_add(usize::try_from(name_bytes).expect("u32 length fits usize"))
        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "rename buffer overflow"))?;
    // `Vec<usize>` supplies native pointer alignment and zero-initializes the
    // full fixed structure plus the variable-length UTF-16 name bytes required
    // by `NtSetInformationFile`.
    let mut buffer = vec![0_usize; buffer_bytes.div_ceil(size_of::<usize>())];
    let info = buffer.as_mut_ptr().cast::<FILE_RENAME_INFORMATION>();
    let information_class = if replace {
        FileRenameInformationEx
    } else {
        FileRenameInformation
    };
    // SAFETY: the usize buffer is sufficiently aligned and sized for the
    // variable-length FILE_RENAME_INFORMATION followed by the UTF-16 component.
    unsafe {
        if replace {

View on GitHub (pinned to affd8760f4)