Hmbown/CodeWhale · error

plugin file is a reparse point, hard link, or non-regular…

Error message

plugin file is a reparse point, hard link, or non-regular file

What it means

On Windows, the plugin loader validates that a plugin file is an ordinary regular file: a real file (not a symlink/junction/reparse point), with exactly one hard link, and not a device or directory. It opens the file and inspects the NTFS file identity (attributes and hard-link count) before accepting it. This guards plugin loading from path-traversal tricks via symlinks or junctions.

Solutions

  1. Replace the symlink/hard link with a real copy of the plugin file at the plugin path.
  2. Remove the reparse point (junction/symlink) and copy the target contents in place.
  3. Point the plugin manifest at the real file location instead of a linked path.
  4. On non-Windows systems this check is compiled out; if on Linux/macOS, verify you are not running a Windows-specific validation path.

Example fix

// before
ln -s ~/dotfiles/my-plugin ~/codewhale/plugins/my-plugin
// after
cp ~/dotfiles/my-plugin ~/codewhale/plugins/my-plugin  // real copy, links == 1
Defensive patterns

Strategy: validation

Validate before calling

// Windows: before handing the plugin path to the loader
let md = std::fs::metadata(&path)?;
assert!(md.is_file(), "plugin path must be a regular file");
assert!(md.file_type().is_symlink() == false, "plugin path must not be a symlink");

Type guard

fn is_plain_file(md: &std::fs::Metadata) -> bool { md.is_file() && !md.file_type().is_symlink() }

Try / catch

match loader_result { Err(e) if e.to_string().contains("reparse point, hard link") => install_real_copy(&path)?, other => other?, }

Prevention

When it happens

Trigger: Calling the plugin file open/validation path (crates/tui/src/plugins/manifest.rs:1573) on Windows when the plugin file is a symlink or junction (FILE_ATTRIBUTE_REPARSE_POINT set), has hard-link count > 1, or metadata().is_file() is false (directory, device, pipe).

Common situations: Plugin installed as a symlink into a dotfiles-managed directory; plugin file hard-linked from a shared cache; plugin path accidentally pointing at a directory; syncing tools (Dropbox, OneDrive) creating reparse placeholders.

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


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/01c1aa096cf5f8b7. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/plugins/manifest.rs:1573

}

#[cfg(windows)]
pub(crate) fn open_bundle_file(path: &Path) -> std::io::Result<fs::File> {
    use std::os::windows::fs::OpenOptionsExt as _;

    const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
    let file = fs::OpenOptions::new()
        .read(true)
        .share_mode(0x0000_0001) // deny concurrent writes and replacement
        .custom_flags(0x0020_0000) // FILE_FLAG_OPEN_REPARSE_POINT
        .open(path)?;
    let metadata = file.metadata()?;
    let identity = windows_file_identity(&file)?;
    if !metadata.is_file()
        || identity.attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
        || identity.links != 1
    {
        return Err(std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "plugin file is a reparse point, hard link, or non-regular file",
        ));
    }
    Ok(file)
}

#[cfg(all(not(unix), not(windows)))]
pub(crate) fn open_bundle_file(path: &Path) -> std::io::Result<fs::File> {
    fs::File::open(path)
}

#[cfg(windows)]
fn open_bundle_directory(path: &Path) -> std::io::Result<fs::File> {
    use std::os::windows::fs::OpenOptionsExt as _;

    const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
    let file = fs::OpenOptions::new()

View on GitHub (pinned to 73e0f67d83)