{"record":{"id":"b3be71906f138cc2","repo":"Hmbown/CodeWhale","slug":"config-lock-path-contains-invalid-unicode-and-c","errorCode":null,"errorMessage":"config lock path {} contains invalid Unicode and cannot be compared safely","messagePattern":"config lock path (.+?) contains invalid Unicode and cannot be compared safely","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/config/src/config_document.rs","lineNumber":289,"sourceCode":"            )\n        });\n    }\n    let actual = OsString::from_wide(&buffer[..written as usize]);\n    if normalize_windows_path_for_comparison(Path::new(&actual))?\n        != normalize_windows_path_for_comparison(expected_path)?\n    {\n        bail!(\n            \"config lock was redirected while opening {}\",\n            crate::quote_os_path(expected_path)\n        );\n    }\n    Ok(())\n}\n\n#[cfg(windows)]\nfn normalize_windows_path_for_comparison(path: &Path) -> Result<String> {\n    let text = path.to_str().ok_or_else(|| {\n        anyhow::anyhow!(\n            \"config lock path {} contains invalid Unicode and cannot be compared safely\",\n            crate::quote_os_path(path)\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\nfn prepare_config_path(path: &Path) -> Result<PathBuf> {\n    let absolute = if path.is_absolute() {\n        path.to_path_buf()","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/config/src/config_document.rs#L271-L307","documentation":"On Windows, after opening the config lock file the code verifies the lock was not redirected by normalizing both paths for comparison (stripping device and UNC prefixes, unifying separators). This error fires when the path cannot be converted to a string because it contains ill-formed Unicode (for example an unpaired surrogate), making a safe comparison impossible.","triggerScenarios":"The Windows-only normalization step runs on the config lock path; Path::to_str returns None when the path holds characters that are not valid Unicode, so the comparison aborts.","commonSituations":"Extremely rare: a junction or directory component created by a buggy script using raw ill-formed UTF-16; a config-directory environment variable holding such a path. Ordinary non-ASCII names (CJK, emoji) are valid Unicode and work fine.","solutions":["Rename the offending directory or component to normal Unicode (or ASCII) using whatever tool created it","Check the environment variable or flag that points Codewhale at its config directory for stray characters","Workaround: point the config directory at a fresh ASCII path via the env var or flag and migrate the files"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// On Windows, verify the config lock path is valid Unicode before locking:\nfn path_is_comparable_windows(p: &std::path::Path) -> bool {\n    p.to_str().is_some()\n}","typeGuard":null,"tryCatchPattern":"On Err, abort startup rather than skipping the redirect check: the comparison exists to catch lock redirection, and an incomparable path cannot be verified. Suggest moving the config directory.","preventionTips":["Keep config directory names plain Unicode (or ASCII)","Avoid creating config paths via raw UTF-16 APIs or unusual junctions"],"tags":["windows","unicode","path","config-lock"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}