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

  1. Rename the credential file (and any invalid component) to a valid Unicode name using only ASCII.
  2. Rebuild the path in Rust from valid UTF-8 strings instead of raw OsString bytes.
  3. Move the file into a freshly created directory with a plain ASCII name and update the configuration.
  4. 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

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


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)