Hmbown/CodeWhale · error
provider catalog lock
Error message
provider catalog lock {} must not be a reparse point What it means
open_cache_lock verifies that the provider catalog lock file on Windows is not a reparse point (symlink, junction, mount point). Reparse points could redirect the lock to an attacker-controlled or unexpected location, so the code refuses to open such a lock file to keep the cache directory trustworthy.
Solutions
- Remove the symlink/junction at the lock path and replace it with a real file (delete and let the app recreate the lock)
- Point the cache/config directory at a real, non-reparse location instead of a symlinked folder
- Copy the cache directory contents to a physical directory and update any redirection
- If the path is on a subst/mounted volume, relocate the catalog cache to a normal local path
Example fix
// before (broken: lock path is a junction) C:\Users\me\.codewhale\cache\catalog.lock -> D:\cache\catalog.lock // after mklink /j removed; real file C:\Users\me\.codewhale\cache\catalog.lock recreated by the app
Defensive patterns
Strategy: validation
Validate before calling
#[cfg(windows)]
fn is_reparse_point(path: &Path) -> std::io::Result<bool> {
use std::os::windows::fs::MetadataExt;
const REPARSE: u32 = 0x0000_0400;
Ok(std::fs::symlink_metadata(path)?.file_attributes() & REPARSE != 0)
}
// call before opening: if is_reparse_point(&lock_path)? { relocate / warn } Try / catch
match open_cache_lock(&path) {
Err(e) if e.to_string().contains("reparse point") => {
// remove the symlink/junction and recreate a real lock file
}
Ok(f) => { /* proceed */ }
Err(e) => return Err(e),
} Prevention
- Do not symlink or junction-redirect the cache/config directory on Windows
- Exclude cache lock files from dotfile-manager symlink farms
- Keep the catalog cache on a physical local volume, not subst/mounted paths
- If you must redirect, redirect the whole app data dir via official settings, not filesystem links
When it happens
Trigger: On Windows, opening the provider catalog cache lock when the file at the cache path has FILE_ATTRIBUTE_REPARSE_POINT set — e.g. the lock file (or an ancestor) was replaced by a symlink or junction, or the cache dir lives on a mounted/substituted path.
Common situations: Users syncing their config/cache directory via symlinks (dotfile managers, OneDrive/Dropbox folder redirection), moving the cache dir with junctions, or CI setups that substitute drives.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Automation lock must not be a reparse point
- Codewhale-owned credential file must be singly linked
- config lock was redirected while opening
- could not inspect workspace .env link count
- external credential path must name a non-reparse regular…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/a0b60a2dd2b8c054.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/provider_catalog_live.rs:555
anyhow::ensure!(
metadata.is_file(),
"provider catalog lock {} must be a regular file",
path.display()
);
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt as _;
anyhow::ensure!(
metadata.nlink() == 1,
"provider catalog lock {} must not be hard linked",
path.display()
);
}
#[cfg(windows)]
{
use std::os::windows::fs::MetadataExt as _;
const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
anyhow::ensure!(
metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT == 0,
"provider catalog lock {} must not be a reparse point",
path.display()
);
}
Ok(file)
}
fn load_from_disk_unlocked_with_limit(path: &Path, max_bytes: u64) -> Option<ProviderCatalogCache> {
let mut options = OpenOptions::new();
options.read(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt as _;
options.custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC | libc::O_NONBLOCK);
}
#[cfg(windows)]
{View on GitHub (pinned to 73e0f67d83)