{"record":{"id":"db716bae7ee0212e","repo":"Hmbown/CodeWhale","slug":"credential-path-contains-invalid-unicode-and-cannot-be","errorCode":null,"errorMessage":"credential path contains invalid Unicode and cannot be compared safely","messagePattern":"credential path contains invalid Unicode and cannot be compared safely","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/external_credentials.rs","lineNumber":347,"sourceCode":"        }\n        if information.nNumberOfLinks != 1 {\n            return Err(io::Error::new(\n                io::ErrorKind::PermissionDenied,\n                \"Codewhale-owned credential file must be singly linked\",\n            ));\n        }\n        verify_windows_owner_only_handle(handle)?;\n    }\n    Ok(file)\n}\n\n/// Normalize a Windows path without replacement characters. Unpaired UTF-16\n/// is rejected so two distinct paths can never compare equal after a lossy\n/// conversion. This is intentionally stricter than filesystem display.\n#[cfg(windows)]\nfn normalize_windows_path_for_comparison(path: &Path) -> io::Result<String> {\n    let text = path.to_str().ok_or_else(|| {\n        io::Error::new(\n            io::ErrorKind::PermissionDenied,\n            \"credential path contains invalid Unicode and cannot be compared safely\",\n        )\n    })?;\n    let without_device_prefix = text.strip_prefix(r\"\\\\?\\\").unwrap_or(text);\n    let normalized_prefix = without_device_prefix.strip_prefix(\"UNC\\\\\").map_or_else(\n        || without_device_prefix.to_string(),\n        |rest| format!(r\"\\\\{rest}\"),\n    );\n    Ok(normalized_prefix\n        .replace('/', \"\\\\\")\n        .trim_end_matches('\\\\')\n        .to_lowercase())\n}\n\n/// Apply a protected DACL granting only the current Windows user full access.\n/// Directories propagate that owner-only policy to newly staged generations.\n#[cfg(all(windows, test))]","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/external_credentials.rs#L329-L365","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet p = PathBuf::from(OsString::from_wide(&wide_with_surrogates));\nlet creds = read_codewhale_owned_to_string(&p)?;\n// after\nlet p = PathBuf::from(\"C:\\\\Users\\\\me\\\\.codewhale\\\\token.json\"); // valid UTF-16/Unicode\nlet creds = read_codewhale_owned_to_string(&p)?;","handlingStrategy":"validation","validationCode":"fn ensure_valid_unicode(path: &Path) -> bool {\n    path.to_str().is_some() // rejects unpaired UTF-16 surrogate paths\n}","typeGuard":"fn has_valid_unicode(path: &Path) -> bool { path.to_str().is_some() }","tryCatchPattern":"if path.to_str().is_none() {\n    eprintln!(\"credential path is not valid Unicode; rename it to an ASCII name\");\n    return Err(...);\n}\nlet creds = read_codewhale_owned_to_string(&path)?;","preventionTips":["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."],"tags":["windows","unicode","encoding","path-validation","credentials"],"backgroundTag":"invalid-argument-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}