Hmbown/CodeWhale · error
Codewhale-owned xAI OAuth path was redirected while opening
Error message
Codewhale-owned xAI OAuth path was redirected while opening
What it means
After opening a handle, the library resolves the handle's final path via GetFinalPathNameByHandle and compares it (normalized) to the expected path. A mismatch means the path was swapped or redirected between path resolution and handle opening — a TOCTOU/planting attack or an actively changing directory — so the operation aborts.
Solutions
- Re-run the operation; if it was a transient race with logout/sync, retry when no other process is touching the directory
- Ensure only one Codewhale instance manages the credentials at a time (stop other instances, pause sync tools)
- Verify the expected path in config matches the real on-disk location (no drives/substitutions mapping it elsewhere)
Example fix
// before: two processes racing on the same store // process A: store.logout()? process B: store.open()? -> redirected path // after: serialize access let _guard = lifecycle_lock.lock().unwrap(); let store = XaiCredentialStore::open(&dir)?;
Defensive patterns
Strategy: retry
Validate before calling
fn same_final_path(handle_path: &str, expected: &str) -> bool {
let norm = |s: &str| s.trim_start_matches(r"\\?\")
.trim_start_matches("UNC").trim_start_matches("\\").to_lowercase();
norm(handle_path) == norm(expected)
} Try / catch
// transient races: retry a few times
for _ in 0..3 {
match XaiCredentialStore::open(&dir) {
Ok(s) => break s,
Err(e) if e.to_string().contains("redirected while opening") => std::thread::sleep(std::time::Duration::from_millis(100)),
Err(e) => return Err(e.into()),
}
} Prevention
- Ensure only one Codewhale process touches the credentials at a time (use the lifecycle lock)
- Pause file-sync tools during login/logout
- Don't rename or replace the credentials directory while operations are in flight
When it happens
Trigger: Opening the credentials directory or file while another process renames/replaces the path (e.g. a concurrent logout deleting and recreating the directory, a sync tool swapping the folder), or the path resolving through a mapping that changes the final name.
Common situations: Running two Codewhale processes where one logs out while the other logs in; antivirus/quarantine replacing the file; a sync client doing atomic rename swaps on the credentials folder during open.
Related errors
- external credential path was redirected while opening
- Codewhale-owned credential file DACL is not…
- Codewhale-owned credential file DACL must grant only one…
- Codewhale-owned credential file must be singly linked
- Codewhale-owned credential file must have an owner-only DACL
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/db7ffe93a972d196.
Report an issue: GitHub.
Appendix: source
Thrown at crates/config/src/xai_credentials.rs:1375
let flags = FILE_NAME_NORMALIZED | VOLUME_NAME_DOS;
let handle = file.as_raw_handle();
// SAFETY: null output asks only 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())
.context("resolving Codewhale-owned xAI OAuth handle path");
}
let mut buffer = vec![0u16; needed as usize + 1];
// SAFETY: the buffer is writable and the handle remains valid.
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())
.context("resolving Codewhale-owned xAI OAuth handle path");
}
let actual = OsString::from_wide(&buffer[..written as usize]);
anyhow::ensure!(
normalize_windows_path_for_comparison(Path::new(&actual))?
== normalize_windows_path_for_comparison(expected)?,
"Codewhale-owned xAI OAuth path was redirected while opening"
);
Ok(metadata)
}
#[cfg(windows)]
fn normalize_windows_path_for_comparison(path: &Path) -> Result<String> {
let text = path.to_str().ok_or_else(|| {
anyhow::anyhow!(
"xAI OAuth 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)