tinyhumansai/openhuman · error · std::io::Error

refusing to follow symlink

Error message

refusing to follow symlink

What it means

The AGENTS.md loader opened the file with O_NOFOLLOW and the kernel rejected it because the path is a symlink. This is the load-time half of the prompt-injection hardening: a symlinked AGENTS.md could redirect agent instructions outside the workspace, so the open itself is refused rather than the file being read and checked afterwards. On Unix the check is atomic (open-time flag); the message comes from the io::Error returned by OpenOptions::open when ELOOP occurs.

Source

Thrown at src/openhuman/agent/prompts/agents_md.rs:211

/// fstat check afterwards). This closes the check-to-open race that a
/// stat-then-`File::open` sequence would leave open. See [`load_agents_md`].
#[cfg(unix)]
fn open_no_follow(path: &Path) -> std::io::Result<std::fs::File> {
    use std::os::unix::fs::OpenOptionsExt;
    std::fs::OpenOptions::new()
        .read(true)
        .custom_flags(libc::O_NOFOLLOW | libc::O_NONBLOCK)
        .open(path)
}

/// Non-Unix fallback: best-effort pre-open symlink check. Windows symlink
/// creation requires elevation / developer mode, so the residual
/// check-to-open race is low risk on these platforms.
#[cfg(not(unix))]
fn open_no_follow(path: &Path) -> std::io::Result<std::fs::File> {
    let meta = std::fs::symlink_metadata(path)?;
    if meta.file_type().is_symlink() {
        return Err(std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            "refusing to follow symlink",
        ));
    }
    std::fs::File::open(path)
}

/// Whether an [`open_no_follow`] error is the "refused a symlink" signal (as
/// opposed to a genuine I/O failure), so the caller can log it distinctly.
#[cfg(unix)]
fn is_symlink_refusal(e: &std::io::Error) -> bool {
    // `O_NOFOLLOW` on a symlink yields `ELOOP` on Linux/macOS and `EMLINK` on
    // some BSDs — either way the open was refused *because* it was a symlink.
    matches!(e.raw_os_error(), Some(v) if v == libc::ELOOP || v == libc::EMLINK)
}

#[cfg(not(unix))]
fn is_symlink_refusal(e: &std::io::Error) -> bool {

View on GitHub (pinned to 7491200858)

Solutions

  1. Inspect the path with ls -la / readlink to confirm it is a symlink and where it points
  2. Replace the symlink with a real file (cp the target over the link) so the loader will read it
  3. If the symlink is intentional and trusted, remove it and configure the content directly in the workspace's AGENTS.md
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at src/openhuman/agent/prompts/agents_md.rs:211 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/ab05a12dc6e5fdbc. Report an issue: GitHub.