{"record":{"id":"14650d665531e936","repo":"Hmbown/CodeWhale","slug":"config-lock-was-redirected-while-opening","errorCode":null,"errorMessage":"config lock was redirected while opening {}","messagePattern":"config lock was redirected while opening (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/config_document.rs","lineNumber":290,"sourceCode":"    let mut buffer = vec![0u16; needed as usize + 1];\n    // SAFETY: `buffer` is writable for its declared length and `handle` stays\n    // valid through the call.\n    let written = unsafe {\n        GetFinalPathNameByHandleW(handle, buffer.as_mut_ptr(), buffer.len() as u32, flags)\n    };\n    if written == 0 || written as usize >= buffer.len() {\n        return Err(std::io::Error::last_os_error()).with_context(|| {\n            format!(\n                \"failed to resolve config lock at {}\",\n                crate::quote_os_path(expected_path)\n            )\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(),","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/config_document.rs#L272-L308","documentation":"This error is thrown by validate_windows_lock_handle when the NT path of the file handle opened for the config write lock does not match the path that was requested. That means something redirected the lock path — typically a symlink or junction swap while opening — so the process may be writing a lock on a different file than intended. The library throws it as a security/integrity guard so concurrent or malicious redirection of the config file cannot silently corrupt state.","triggerScenarios":"Calling with_config_write_lock on Windows when the target config path is a symbolic link, a junction, or is replaced by one between path resolution and handle validation; also when an antivirus/sync tool (Dropbox, OneDrive) swaps the file for a redirect during opening.","commonSituations":"Config files inside symlinked directories (e.g. dotfiles managed via symlinks), redirected profiles, or filesystem watchers that atomically replace config.toml with a link while the app tries to take the write lock.","solutions":["Remove any symlink or junction on the config path so the real file is opened directly","Retry the lock operation; if the file was being atomically swapped, a later attempt sees the stable file","Exclude the config directory from sync/AV tools that replace files with links","Check the path with `fsutil reparsepoint query` (Windows) to confirm redirection"],"exampleFix":"// before: config.toml is a symlink to dotfiles/config.toml\nC:\\Users\\me\\.config\\codewhale\\config.toml -> dotfiles\\config.toml\n// after: replace the link with a real file copy\ncopy dotfiles\\config.toml C:\\Users\\me\\.config\\codewhale\\config.toml\ndel C:\\Users\\me\\.config\\codewhale\\config.toml (the link), keep the copied real file","handlingStrategy":"try-catch","validationCode":"// Windows: check for reparse points before locking\nuse std::os::windows::fs::MetadataExt;\nlet m = std::fs::metadata(path)?;\nif m.file_attributes() & 0x400 != 0 { // FILE_ATTRIBUTE_REPARSE_POINT\n    // resolve to the real path first\n}\nlet real = std::fs::canonicalize(path)?;","typeGuard":"fn is_reparse_point(p: &std::path::Path) -> bool {\n    std::os::windows::fs::MetadataExt::file_attributes(&std::fs::metadata(p).unwrap_or_else(|_| panic!())) & 0x400 != 0\n}","tryCatchPattern":"match with_config_write_lock(&path) {\n    Err(e) if e.to_string().contains(\"redirected\") => {\n        // resolve symlink/canonicalize the path and retry\n        let real = std::fs::canonicalize(&path)?;\n        with_config_write_lock(&real)?\n    }\n    other => other?,\n}","preventionTips":["Keep the config file a real file, not a symlink or junction","Exclude the config directory from sync/backup tools that do atomic link swaps","Canonicalize paths before locking"],"tags":["windows","filesystem","security","locking"],"backgroundTag":"path-traversal-blocked","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"}