Hmbown/CodeWhale · error · io::Error
credential path contains invalid Unicode and cannot be…
Error message
credential path contains invalid Unicode and cannot be compared safely
What it means
normalize_windows_path_for_comparison must produce an exact, lossless string to compare the requested and actual paths; if the Path contains invalid UTF-16 (unpaired surrogates), to_str() fails and the library throws PermissionDenied rather than compare two distinct paths as equal after a lossy U+FFFD replacement. This is deliberately stricter than the filesystem's display behavior.
Solutions
- Rename the credential file (and any invalid component) to a valid Unicode name using only ASCII.
- Rebuild the path in Rust from valid UTF-8 strings instead of raw OsString bytes.
- Move the file into a freshly created directory with a plain ASCII name and update the configuration.
- Do not use lossy conversion to "fix" this; the check is intentional.
Example fix
// before
let p = PathBuf::from(OsString::from_wide(&wide_with_surrogates));
let creds = read_codewhale_owned_to_string(&p)?;
// after
let p = PathBuf::from("C:\\Users\\me\\.codewhale\\token.json"); // valid UTF-16/Unicode
let creds = read_codewhale_owned_to_string(&p)?; Defensive patterns
Strategy: validation
Validate before calling
fn ensure_valid_unicode(path: &Path) -> bool {
path.to_str().is_some() // rejects unpaired UTF-16 surrogate paths
} Type guard
fn has_valid_unicode(path: &Path) -> bool { path.to_str().is_some() } Try / catch
if path.to_str().is_none() {
eprintln!("credential path is not valid Unicode; rename it to an ASCII name");
return Err(...);
}
let creds = read_codewhale_owned_to_string(&path)?; Prevention
- Name credential files with plain ASCII characters.
- Build paths from valid UTF-8/Unicode strings, never raw OsString bytes from legacy sources.
- If a legacy filename contains surrogates, rename it before configuring it.
- Do not attempt lossy conversions to work around this check.
When it happens
Trigger: open_secure_regular_file is given a path whose OsStr contains unpaired UTF-16 code units — usually produced by names created from raw bytes/WCHAR sequences that are not valid Unicode.
Common situations: A path built from non-UTF-8 command-line input or a legacy tool created a file name with surrogate characters; a path was round-tripped through a system that mangled encoding; unusual localized filenames created by old software.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- xAI OAuth path contains invalid Unicode and cannot be…
- 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/db716bae7ee0212e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/external_credentials.rs:347
}
if information.nNumberOfLinks != 1 {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"Codewhale-owned credential file must be singly linked",
));
}
verify_windows_owner_only_handle(handle)?;
}
Ok(file)
}
/// Normalize a Windows path without replacement characters. Unpaired UTF-16
/// is rejected so two distinct paths can never compare equal after a lossy
/// conversion. This is intentionally stricter than filesystem display.
#[cfg(windows)]
fn normalize_windows_path_for_comparison(path: &Path) -> io::Result<String> {
let text = path.to_str().ok_or_else(|| {
io::Error::new(
io::ErrorKind::PermissionDenied,
"credential path contains invalid Unicode and cannot be compared safely",
)
})?;
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(),
|rest| format!(r"\\{rest}"),
);
Ok(normalized_prefix
.replace('/', "\\")
.trim_end_matches('\\')
.to_lowercase())
}
/// Apply a protected DACL granting only the current Windows user full access.
/// Directories propagate that owner-only policy to newly staged generations.
#[cfg(all(windows, test))]View on GitHub (pinned to 73e0f67d83)