Hmbown/CodeWhale · error
skill state lock at must be a regular, non-reparse file
Error message
skill state lock at {} must be a regular, non-reparse file What it means
On Windows, the skill state lock file must be a regular file with no NTFS reparse point (symlink, junction, mount point). validate_state_lock inspects the file's metadata attributes and fails the anyhow::ensure! check if the file is not a regular file or carries the FILE_ATTRIBUTE_REPARSE_POINT attribute. This prevents an attacker from swapping the lock file for a link to a sensitive target.
Solutions
- Remove the lock file and replace it with a real regular file (delete and let the app recreate it).
- Stop symlinking or junctioning the skill state directory; copy the real directory instead.
- Check attributes with `fsutil reparsepoint query <path>` or `dir /AL` and remove the reparse point (`fsutil reparsepoint delete <path>`).
- Exclude the state directory from OneDrive/cloud placeholder sync.
Example fix
// before ln -s ~/dotfiles/skill-state.lock ~/.local/share/codewhale/skill-state.lock // after cp ~/dotfiles/skill-state.lock ~/.local/share/codewhale/skill-state.lock
Defensive patterns
Strategy: validation
Validate before calling
const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
let md = std::fs::metadata(lock_path)?;
let is_plain = md.is_file() && md.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT == 0;
if !is_plain { /* recreate the lock file or refuse */ } Type guard
fn is_plain_lock(md: &std::fs::Metadata) -> bool {
md.is_file() && md.file_attributes() & 0x0000_0400 == 0
} Prevention
- Never symlink or junction the skill state directory on Windows.
- Exclude the state directory from OneDrive/cloud-sync placeholders.
- Check `dir /AL` in the state directory periodically for reparse points.
- Recreate the lock as a plain file rather than restoring it from a synced copy.
When it happens
Trigger: open_state_lock is called and the lock file at the given path either is not a regular file or has the Windows reparse-point attribute set (e.g. it is a symlink, junction, or dedup placeholder).
Common situations: Users symlink their skills/state directories into cloud-synced or versioned folders (Dropbox, dotfile repos with symlinks), or use junctions to relocate state to another drive; Windows dev-container or OneDrive placeholders also carry reparse attributes.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- Automation lock must not be a reparse point
- external credential path must name a non-reparse regular…
- is a reparse point, not a workspace-owned file
- plugin directory is a reparse point or non-directory
- plugin identity probe found a reparse point, hard link, or…
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/859118ea7685556e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/skill_state.rs:228
.metadata()
.with_context(|| format!("inspect skill state lock at {}", path.display()))?;
anyhow::ensure!(
metadata.is_file() && metadata.nlink() == 1,
"skill state lock at {} must be one regular, non-hard-linked file",
path.display()
);
Ok(())
}
#[cfg(windows)]
fn validate_state_lock(path: &Path, file: &fs::File) -> Result<()> {
use std::os::windows::fs::MetadataExt as _;
const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
let metadata = file
.metadata()
.with_context(|| format!("inspect skill state lock at {}", path.display()))?;
anyhow::ensure!(
metadata.is_file() && metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT == 0,
"skill state lock at {} must be a regular, non-reparse file",
path.display()
);
Ok(())
}
#[cfg(all(not(unix), not(windows)))]
fn validate_state_lock(path: &Path, file: &fs::File) -> Result<()> {
anyhow::ensure!(
file.metadata()
.with_context(|| format!("inspect skill state lock at {}", path.display()))?
.is_file(),
"skill state lock at {} must be a regular file",
path.display()
);
Ok(())
}View on GitHub (pinned to 433685b202)