{"record":{"id":"f7edbc70b9b89e3b","repo":"atuinsh/atuin","slug":"tail-target-is-not-a-regular-file","errorCode":null,"errorMessage":"tail target is not a regular file","messagePattern":"tail target is not a regular file","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/atuin-common/src/fs/tail.rs","lineNumber":300,"sourceCode":"    }\n\n    fn reset(&mut self) {\n        self.schedule.reset();\n    }\n}\n\nstruct FileSource {\n    file: tokio::fs::File,\n    path: PathBuf,\n    pos: u64,\n    buf: BytesMut,\n}\n\nimpl FileSource {\n    async fn open(path: PathBuf) -> io::Result<Self> {\n        let file = tokio::fs::File::open(&path).await?;\n        if !file.metadata().await?.is_file() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"tail target is not a regular file\",\n            ));\n        }\n        Ok(Self {\n            file,\n            path,\n            pos: 0,\n            buf: BytesMut::new(),\n        })\n    }\n}\n\nimpl TailSource for FileSource {\n    async fn read_at(&mut self, offset: u64, len: usize) -> io::Result<Bytes> {\n        // Reads walk forward sequentially, so the cursor is usually already at `offset`; only\n        // seek when it is not (resume, anchor probe, rotation).\n        if self.pos != offset {","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/atuinsh/atuin/blob/900007c48045867e7f44b0f9bc55ad9dbe99ee0d/crates/atuin-common/src/fs/tail.rs#L282-L318","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet source = FileSource::open(PathBuf::from(\"/var/log\")).await?; // directory\n\n// after\nlet path = PathBuf::from(\"/var/log\");\nlet meta = tokio::fs::metadata(&path).await?;\nif !meta.is_file() {\n    anyhow::bail!(\"{} is not a regular file; pass a concrete log file\", path.display());\n}\nlet source = FileSource::open(path).await?;","handlingStrategy":"validation","validationCode":"let meta = std::fs::metadata(&path)?;\nif !meta.is_file() {\n    return Err(anyhow::anyhow!(\"refusing to tail {}: not a regular file\", path.display()));\n}","typeGuard":"fn is_regular_file(path: &std::path::Path) -> bool {\n    std::fs::metadata(path).map(|m| m.is_file()).unwrap_or(false)\n}","tryCatchPattern":"match FileSource::open(path).await {\n    Ok(src) => src,\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {\n        eprintln!(\"{} is not a regular file; pass a concrete log file\", path.display());\n        return Ok(());\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["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."],"tags":["filesystem","io","validation"],"backgroundTag":"incompatible-source-type","analyzedSha":"900007c48045867e7f44b0f9bc55ad9dbe99ee0d","analyzedAt":"2026-09-19T12:52:51.491Z","contentChangedAt":"2026-09-19T12:52:51.491Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}