Hmbown/CodeWhale · error · anyhow::Error

refusing non-regular or reparse-point config lock at {}

Error message

refusing non-regular or reparse-point config lock at {}

What it means

Windows-only hardening of the config write lock: after opening the expected lock file, its metadata is inspected and the open is refused if the file is not a plain regular file or carries FILE_ATTRIBUTE_REPARSE_POINT (symlink/junction). This blocks symlink-swap and lock-redirection attacks where an attacker points the lock elsewhere so two writers can hold 'different' locks for the same config.

Source

Thrown at crates/config/src/config_document.rs:241

    use std::ffi::OsString;
    use std::os::windows::ffi::OsStringExt as _;
    use std::os::windows::fs::MetadataExt as _;
    use std::os::windows::io::AsRawHandle as _;
    use windows_sys::Win32::Storage::FileSystem::{
        FILE_ATTRIBUTE_REPARSE_POINT, FILE_NAME_NORMALIZED, GetFinalPathNameByHandleW,
        VOLUME_NAME_DOS,
    };

    let metadata = file.metadata().with_context(|| {
        format!(
            "failed to inspect config lock at {}",
            crate::quote_os_path(expected_path)
        )
    })?;
    if !metadata.file_type().is_file()
        || metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0
    {
        bail!(
            "refusing non-regular or reparse-point config lock at {}",
            crate::quote_os_path(expected_path)
        );
    }

    let handle = file.as_raw_handle();
    let flags = FILE_NAME_NORMALIZED | VOLUME_NAME_DOS;
    // SAFETY: `handle` remains owned by `file`; a null output buffer asks for
    // the required UTF-16 length.
    let needed = unsafe { GetFinalPathNameByHandleW(handle, std::ptr::null_mut(), 0, flags) };
    if needed == 0 {
        return Err(std::io::Error::last_os_error()).with_context(|| {
            format!(
                "failed to resolve config lock at {}",
                crate::quote_os_path(expected_path)
            )
        });
    }

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Delete the offending lock file (it is transient and safe to remove when no Codewhale process is running) so a regular file is recreated
  2. Remove the symlink/junction at or above the config directory so the lock path is a direct NTFS path
  3. Move the config directory to a non-redirected location and point CODEWHALE/config at it
  4. If you intentionally redirect config via junctions, exclude the lock file name from the redirection

Example fix

# before
C:\Users\me\.config\codewhale\.config.toml.lock -> symlink into dotfiles repo (reparse point)

# after (PowerShell, no Codewhale running)
Remove-Item -Force C:\Users\me\.config\codewhale\*.lock
# next config write recreates a regular lock file
Defensive patterns

Strategy: validation

Validate before calling

// On Windows, before relying on the config lock, verify it is a regular file:
#[cfg(windows)]
fn lock_is_regular(p: &std::path::Path) -> bool {
    use std::os::windows::fs::MetadataExt;
    std::fs::symlink_metadata(p)
        .map(|m| m.file_type().is_file() && (m.file_attributes() & 0x400) == 0) // FILE_ATTRIBUTE_REPARSE_POINT
        .unwrap_or(true) // absent lock is fine; it will be created
}

Try / catch

match with_config_write_lock(&path, op) {
    Ok(v) => v,
    Err(e) if e.to_string().contains("reparse-point config lock") => {
        // instruct: delete the lock file, remove symlink indirection, retry once
        user_action_then_retry(e)
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: The expected lock path on Windows resolves to a directory, a symlink/junction/hardlink-style reparse point, or another non-regular file kind when with_config_write_lock opens it.

Common situations: Config directory inside a symlinked/junctioned path (e.g. dotfiles managers, redirected profile, subst drives) that materialized the lock itself as a link; leftover malicious or tool-created symlink at the lock location; copying a config tree that preserved reparse points.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/62eef92b0b9552f4. Report an issue: GitHub.