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

  1. Re-run the operation; if it was a transient race with logout/sync, retry when no other process is touching the directory
  2. Ensure only one Codewhale instance manages the credentials at a time (stop other instances, pause sync tools)
  3. 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

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


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)