Hmbown/CodeWhale · error

private sidecar file

Error message

private sidecar file {path} must have exactly one filesystem link

What it means

On Windows, after confirming the sidecar is a regular non-reparse file, session_manager checks via GetFileInformationByHandle that nNumberOfLinks is exactly 1. More than one link means the file is hard-linked elsewhere, so another path could observe or tamper with the private file; such files are rejected as InvalidData.

Solutions

  1. Find the extra links (e.g. `fsutil hardlink list <path>`) and delete them, leaving one link.
  2. Restore the file from a copy that is not hardlinked.
  3. Disable hardlink-based dedupe/backup for the sessions directory.

Example fix

// before: file duplicated via hardlink by backup tool
fsutil hardlink list session.lock  // shows 2 links
// after: keep one link
copy session.lock session.lock.new && move /y session.lock.new session.lock
Defensive patterns

Strategy: validation

Validate before calling

// Windows: reject hardlinked files before opening
fn has_single_link(p: &Path) -> bool {
    std::fs::metadata(p).map(|m| m.number_of_links() == Some(1)).unwrap_or(false)
}

Try / catch

match open_private_read_file(path) {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => eprintln!("hardlink detected, restore a single-link copy"),
    other => other?,
}

Prevention

When it happens

Trigger: Calling open_private_lock_file or open_private_read_file on Windows when the sidecar file has 2+ hard links (nNumberOfLinks != 1).

Common situations: A hardlink-creating backup tool (e.g. rsync-like dedupe, Time-Machine-style snapshots on NTFS) linked the session file; the user hardlinked files manually; a restore tool reconstructed the tree with hardlinks.

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@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/f46fadc474e41a2d. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/session_manager.rs:226

    };

    let metadata = file.metadata()?;
    if !metadata.is_file() || metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!(
                "private sidecar file {} must be a non-reparse regular file",
                path.display()
            ),
        ));
    }
    let mut info = BY_HANDLE_FILE_INFORMATION::default();
    // SAFETY: `file` keeps the handle valid and `info` is writable for the call.
    if unsafe { GetFileInformationByHandle(file.as_raw_handle(), &mut info) } == 0 {
        return Err(io::Error::last_os_error());
    }
    if info.nNumberOfLinks != 1 {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!(
                "private sidecar file {} must have exactly one filesystem link",
                path.display()
            ),
        ));
    }
    Ok(())
}

#[cfg(all(not(unix), not(windows)))]
fn validate_private_regular_file(file: &fs::File, path: &Path) -> io::Result<()> {
    if !file.metadata()?.is_file() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!("private sidecar file {} must be regular", path.display()),
        ));
    }

View on GitHub (pinned to 73e0f67d83)