Hmbown/CodeWhale · error · io::Error
external credential path was redirected while opening
Error message
external credential path was redirected while opening
What it means
After opening, the library resolves the final path of the opened handle (GetFinalPathNameByHandle-style) and compares it with the requested path; a mismatch means the filesystem redirected the open (component replaced between validation and open, or a mapped drive/device path divergence). It throws PermissionDenied to guarantee the file read is exactly the one validated.
Solutions
- Re-run the read; if it is a race, eliminate the process modifying the path (pause sync tooling) and keep the credential file stable.
- Use the physical path: replace mapped drive letters with the UNC physical path or a local path under the user profile.
- Avoid SUBST/mapped-drive aliases for credential storage; store on a fixed local path.
- Check for 8.3 short names (PROGRA~1) in the configured path and use the long form.
Example fix
// before
let creds = read_codewhale_owned_to_string(Path::new("Z:\\secrets\\token.json"))?; // mapped drive
// after
let creds = read_codewhale_owned_to_string(Path::new("C:\\Users\\me\\.codewhale\\token.json"))?; Defensive patterns
Strategy: retry
Validate before calling
// Ensure the configured path is a local physical path, not a mapped/SUBST drive: // net use (look for the drive letter) and subst (list aliases) before configuring.
Try / catch
match read_codewhale_owned_to_string(&path) {
Ok(creds) => use(creds),
Err(e) if e.to_string().contains("redirected while opening") => {
// transient TOCTOU: stop whatever rewrites the path, then retry once
eprintln!("credential path changed during open; retrying once");
read_codewhale_owned_to_string(&path).map(use_rest)
}
Err(e) => return Err(e),
} Prevention
- Use physical local paths (C:\...) or UNC paths, not mapped drives or SUBST aliases.
- Keep the credential path free of tools that rewrite/replace it (sync clients, AV quarantine).
- Avoid 8.3 short names in configured paths; use the full long form.
- Retry once on this error; persistent failures mean something is actively modifying the path.
When it happens
Trigger: A path component is swapped or a reparse point is introduced between the pre-open checks and the final open (TOCTOU race); a mapped network drive or SUBST drive resolves to a different canonical path than requested; Windows short (8.3) names or different device prefixes cause the normalized comparison to diverge unexpectedly.
Common situations: Another process (sync tool, antivirus quarantine/restore, installer) rewrote the credential path mid-open; the credential lives on a mapped drive (Z:\) whose canonical \\server\share form differs; SUBST aliases are in play.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Codewhale-owned credential file must be singly linked
- Codewhale-owned xAI OAuth path was redirected while opening
- external credential path must name a non-reparse regular…
- Automation lock must not be a reparse point
- Codewhale-owned credential file DACL is not…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/2e474a6f66dc3ba7.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/external_credentials.rs:315
// the required UTF-16 buffer length.
let needed = unsafe { GetFinalPathNameByHandleW(handle, std::ptr::null_mut(), 0, flags) };
if needed == 0 {
return Err(io::Error::last_os_error());
}
let mut buffer = vec![0u16; needed as usize + 1];
// SAFETY: `buffer` is writable for its declared length and `handle` is
// valid for the duration of 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(io::Error::last_os_error());
}
let final_path = OsString::from_wide(&buffer[..written as usize]);
let actual = normalize_windows_path_for_comparison(Path::new(&final_path))?;
let expected = normalize_windows_path_for_comparison(path)?;
if actual != expected {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"external credential path was redirected while opening",
));
}
if require_owner_only {
use windows_sys::Win32::Storage::FileSystem::{
BY_HANDLE_FILE_INFORMATION, GetFileInformationByHandle,
};
let mut information = BY_HANDLE_FILE_INFORMATION::default();
// SAFETY: the opened credential handle and output pointer remain valid
// for the duration of the call.
if unsafe { GetFileInformationByHandle(handle, &mut information) } == 0 {
return Err(io::Error::last_os_error());
}
if information.nNumberOfLinks != 1 {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"Codewhale-owned credential file must be singly linked",View on GitHub (pinned to 73e0f67d83)