Hmbown/CodeWhale · error

config lock was redirected while opening

Error message

config lock was redirected while opening {}

What it means

This error is thrown by validate_windows_lock_handle when the NT path of the file handle opened for the config write lock does not match the path that was requested. That means something redirected the lock path — typically a symlink or junction swap while opening — so the process may be writing a lock on a different file than intended. The library throws it as a security/integrity guard so concurrent or malicious redirection of the config file cannot silently corrupt state.

Solutions

  1. Remove any symlink or junction on the config path so the real file is opened directly
  2. Retry the lock operation; if the file was being atomically swapped, a later attempt sees the stable file
  3. Exclude the config directory from sync/AV tools that replace files with links
  4. Check the path with `fsutil reparsepoint query` (Windows) to confirm redirection

Example fix

// before: config.toml is a symlink to dotfiles/config.toml
C:\Users\me\.config\codewhale\config.toml -> dotfiles\config.toml
// after: replace the link with a real file copy
copy dotfiles\config.toml C:\Users\me\.config\codewhale\config.toml
del C:\Users\me\.config\codewhale\config.toml (the link), keep the copied real file
Defensive patterns

Strategy: try-catch

Validate before calling

// Windows: check for reparse points before locking
use std::os::windows::fs::MetadataExt;
let m = std::fs::metadata(path)?;
if m.file_attributes() & 0x400 != 0 { // FILE_ATTRIBUTE_REPARSE_POINT
    // resolve to the real path first
}
let real = std::fs::canonicalize(path)?;

Type guard

fn is_reparse_point(p: &std::path::Path) -> bool {
    std::os::windows::fs::MetadataExt::file_attributes(&std::fs::metadata(p).unwrap_or_else(|_| panic!())) & 0x400 != 0
}

Try / catch

match with_config_write_lock(&path) {
    Err(e) if e.to_string().contains("redirected") => {
        // resolve symlink/canonicalize the path and retry
        let real = std::fs::canonicalize(&path)?;
        with_config_write_lock(&real)?
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling with_config_write_lock on Windows when the target config path is a symbolic link, a junction, or is replaced by one between path resolution and handle validation; also when an antivirus/sync tool (Dropbox, OneDrive) swaps the file for a redirect during opening.

Common situations: Config files inside symlinked directories (e.g. dotfiles managed via symlinks), redirected profiles, or filesystem watchers that atomically replace config.toml with a link while the app tries to take the write lock.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/14650d665531e936. Report an issue: GitHub.

Appendix: source

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

    let mut buffer = vec![0u16; needed as usize + 1];
    // SAFETY: `buffer` is writable for its declared length and `handle` stays
    // valid through the call.
    let written = unsafe {
        GetFinalPathNameByHandleW(handle, buffer.as_mut_ptr(), buffer.len() as u32, flags)
    };
    if written == 0 || written as usize >= buffer.len() {
        return Err(std::io::Error::last_os_error()).with_context(|| {
            format!(
                "failed to resolve config lock at {}",
                crate::quote_os_path(expected_path)
            )
        });
    }
    let actual = OsString::from_wide(&buffer[..written as usize]);
    if normalize_windows_path_for_comparison(Path::new(&actual))?
        != normalize_windows_path_for_comparison(expected_path)?
    {
        bail!(
            "config lock was redirected while opening {}",
            crate::quote_os_path(expected_path)
        );
    }
    Ok(())
}

#[cfg(windows)]
fn normalize_windows_path_for_comparison(path: &Path) -> Result<String> {
    let text = path.to_str().ok_or_else(|| {
        anyhow::anyhow!(
            "config lock path {} contains invalid Unicode and cannot be compared safely",
            crate::quote_os_path(path)
        )
    })?;
    let without_device_prefix = text.strip_prefix(r"\\?\").unwrap_or(text);
    let normalized_prefix = without_device_prefix.strip_prefix("UNC\\").map_or_else(
        || without_device_prefix.to_string(),

View on GitHub (pinned to 73e0f67d83)