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
- Remove any symlink or junction on the config path so the real file is opened directly
- Retry the lock operation; if the file was being atomically swapped, a later attempt sees the stable file
- Exclude the config directory from sync/AV tools that replace files with links
- 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
- Keep the config file a real file, not a symlink or junction
- Exclude the config directory from sync/backup tools that do atomic link swaps
- Canonicalize paths before locking
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
- Automation lock must not be a reparse point
- Codewhale-owned credential file must be singly linked
- could not inspect workspace .env link count
- external credential path must name a non-reparse regular…
- external credential path was redirected while opening
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)