atuinsh/atuin · error · std::io::Error

tail target is not a regular file

Error message

tail target is not a regular file

What it means

`FileSource::open` in the tail module opens the target path with `tokio::fs::File::open` and then verifies via `file.metadata().is_file()` that it is a regular file. If the path is a directory, FIFO, socket, device, or other non-regular file, it returns an InvalidInput io::Error with this message. Tail-following only supports regular files because it seeks and reads appended bytes.

Solutions

  1. Check the target with `std::fs::metadata(path)?.is_file()` before tailing and correct the path to an actual file.
  2. If the target is a directory, choose the concrete file inside it (e.g. the specific .log file).
  3. If you need to tail a pipe/fifo output, redirect it to a regular file first (e.g. `cmd > /tmp/out.log`) and tail that file.
  4. On platforms where the path may be a symlink to a special file, resolve and verify the final target type.

Example fix

// before
let source = FileSource::open(PathBuf::from("/var/log")).await?; // directory

// after
let path = PathBuf::from("/var/log");
let meta = tokio::fs::metadata(&path).await?;
if !meta.is_file() {
    anyhow::bail!("{} is not a regular file; pass a concrete log file", path.display());
}
let source = FileSource::open(path).await?;
Defensive patterns

Strategy: validation

Validate before calling

let meta = std::fs::metadata(&path)?;
if !meta.is_file() {
    return Err(anyhow::anyhow!("refusing to tail {}: not a regular file", path.display()));
}

Type guard

fn is_regular_file(path: &std::path::Path) -> bool {
    std::fs::metadata(path).map(|m| m.is_file()).unwrap_or(false)
}

Try / catch

match FileSource::open(path).await {
    Ok(src) => src,
    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {
        eprintln!("{} is not a regular file; pass a concrete log file", path.display());
        return Ok(());
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling the tail API (`FileSource::open` / tail lines/follow) with a path pointing to a directory, named pipe (e.g. /dev/stdout, process substitution `<(cmd)`), socket, or device node like /dev/null.

Common situations: Passing a log directory instead of a log file; trying to tail a process substitution or pipe; pointing at /proc or /sys pseudo-files which are not regular files.

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 atuinsh/atuin@900007c480 (2026-09-19). Data as JSON: /api/errors/f7edbc70b9b89e3b. Report an issue: GitHub.

Appendix: source

Thrown at crates/atuin-common/src/fs/tail.rs:300

    }

    fn reset(&mut self) {
        self.schedule.reset();
    }
}

struct FileSource {
    file: tokio::fs::File,
    path: PathBuf,
    pos: u64,
    buf: BytesMut,
}

impl FileSource {
    async fn open(path: PathBuf) -> io::Result<Self> {
        let file = tokio::fs::File::open(&path).await?;
        if !file.metadata().await?.is_file() {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "tail target is not a regular file",
            ));
        }
        Ok(Self {
            file,
            path,
            pos: 0,
            buf: BytesMut::new(),
        })
    }
}

impl TailSource for FileSource {
    async fn read_at(&mut self, offset: u64, len: usize) -> io::Result<Bytes> {
        // Reads walk forward sequentially, so the cursor is usually already at `offset`; only
        // seek when it is not (resume, anchor probe, rotation).
        if self.pos != offset {

View on GitHub (pinned to 900007c480)