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
- Check the target with `std::fs::metadata(path)?.is_file()` before tailing and correct the path to an actual file.
- If the target is a directory, choose the concrete file inside it (e.g. the specific .log file).
- 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.
- 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
- Always stat the target with metadata().is_file() before tailing.
- Do not pass directories, fifos, /proc or /sys entries to tail APIs.
- Redirect pipe/process-substitution output to a real file before tailing.
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
- Config key must be non-empty and must not contain whitespace
- Could not find history directory
- Could not find history directory
- Could not find history file
- Could not find history file
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)