{"record":{"id":"859118ea7685556e","repo":"Hmbown/CodeWhale","slug":"skill-state-lock-at-must-be-a-regular-non-reparse-file","errorCode":null,"errorMessage":"skill state lock at {} must be a regular, non-reparse file","messagePattern":"skill state lock at (.+?) must be a regular, non-reparse file","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/skill_state.rs","lineNumber":228,"sourceCode":"        .metadata()\n        .with_context(|| format!(\"inspect skill state lock at {}\", path.display()))?;\n    anyhow::ensure!(\n        metadata.is_file() && metadata.nlink() == 1,\n        \"skill state lock at {} must be one regular, non-hard-linked file\",\n        path.display()\n    );\n    Ok(())\n}\n\n#[cfg(windows)]\nfn validate_state_lock(path: &Path, file: &fs::File) -> Result<()> {\n    use std::os::windows::fs::MetadataExt as _;\n\n    const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;\n    let metadata = file\n        .metadata()\n        .with_context(|| format!(\"inspect skill state lock at {}\", path.display()))?;\n    anyhow::ensure!(\n        metadata.is_file() && metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT == 0,\n        \"skill state lock at {} must be a regular, non-reparse file\",\n        path.display()\n    );\n    Ok(())\n}\n\n#[cfg(all(not(unix), not(windows)))]\nfn validate_state_lock(path: &Path, file: &fs::File) -> Result<()> {\n    anyhow::ensure!(\n        file.metadata()\n            .with_context(|| format!(\"inspect skill state lock at {}\", path.display()))?\n            .is_file(),\n        \"skill state lock at {} must be a regular file\",\n        path.display()\n    );\n    Ok(())\n}","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/skill_state.rs#L210-L246","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nln -s ~/dotfiles/skill-state.lock ~/.local/share/codewhale/skill-state.lock\n// after\ncp ~/dotfiles/skill-state.lock ~/.local/share/codewhale/skill-state.lock","handlingStrategy":"validation","validationCode":"const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;\nlet md = std::fs::metadata(lock_path)?;\nlet is_plain = md.is_file() && md.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT == 0;\nif !is_plain { /* recreate the lock file or refuse */ }","typeGuard":"fn is_plain_lock(md: &std::fs::Metadata) -> bool {\n    md.is_file() && md.file_attributes() & 0x0000_0400 == 0\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["windows","filesystem","symlink","security"],"backgroundTag":"incompatible-source-type","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T01:17:13.364Z"}