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

  1. Remove the lock file and replace it with a real regular file (delete and let the app recreate it).
  2. Stop symlinking or junctioning the skill state directory; copy the real directory instead.
  3. Check attributes with `fsutil reparsepoint query <path>` or `dir /AL` and remove the reparse point (`fsutil reparsepoint delete <path>`).
  4. 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

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


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)