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
- Replace the symlink/hard link with a real copy of the plugin file at the plugin path.
- Remove the reparse point (junction/symlink) and copy the target contents in place.
- Point the plugin manifest at the real file location instead of a linked path.
- 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
- Never symlink or junction plugin directories managed by dotfile tools; copy instead
- Avoid hard-link deduplicators (e.g. jdupes -L) on plugin directories
- Document that plugin paths must be plain real files on Windows
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
- external credential path must name a non-reparse regular…
- Fleet file must be regular and not linked
- late usage store must be a real directory
- 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@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)