affaan-m/ECC · error
must be a regular file
Error message
{label} must be a regular file What it means
A bounded-file-reading helper in the CLI opens a user-supplied path and verifies via `File::metadata()` that it is a regular file before reading. Directories, FIFOs, device nodes, and other special files are rejected so reads are size-bounded and deterministic. The error message uses the caller-provided `label` to identify which input failed.
Solutions
- Point the argument at an actual regular file, not a directory or special file.
- Verify the path exists and is a file with `ls -l` / `file <path>` before invoking.
- If reading a directory was intended, enumerate its files and pass individual file paths instead.
Example fix
// before
let content = read_bounded_file(path, "plan", max_bytes)?;
// after
let meta = std::fs::metadata(path)?;
anyhow::ensure!(meta.is_file(), "expected a file, got: {}", path.display());
let content = read_bounded_file(path, "plan", max_bytes)?; Defensive patterns
Strategy: validation
Validate before calling
let meta = std::fs::metadata(path)
.with_context(|| format!("cannot access {}", path.display()))?;
anyhow::ensure!(meta.is_file(), "{} is not a regular file", path.display()); Type guard
fn is_regular_file(p: &std::path::Path) -> bool {
std::fs::metadata(p).map(|m| m.is_file()).unwrap_or(false)
} Try / catch
match read_bounded_file(path, label, max_bytes) {
Ok(content) => content,
Err(e) if e.to_string().contains("must be a regular file") => {
eprintln!("{}: pass a regular file path, not a directory/special file", path.display());
std::process::exit(2);
}
Err(e) => return Err(e),
} Prevention
- Use shell/tab completion so directory arguments are not mistyped
- Pre-flight check paths with `file <path>` in scripts
- Symlink-following metadata is used, so verify final target is a file
When it happens
Trigger: Passing a directory path, a named pipe, /dev/null-style device file, or a symlink resolving to a non-regular file to the bounded input reader (e.g. a CLI flag pointing at a directory).
Common situations: Typo'd CLI argument where a directory was given instead of a file, piping setups where the path is a FIFO, or platform-specific special files being used as input.
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
- exceeds the -byte limit
- all overlays must be readable local files
- all takes must be readable local files
- base video must be a readable local file
- --config-dir must exist and contain a regular sixtytwo.yaml…
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/9c7fdd8e8a8c66ad.
Report an issue: GitHub.
Appendix: source
Thrown at ecc2/src/main.rs:1409
details: BTreeMap<String, String>,
}
fn read_bounded_file(path: &Path, max_bytes: u64, label: &str) -> Result<Vec<u8>> {
let mut options = File::options();
options.read(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.custom_flags(libc::O_NONBLOCK);
}
let file = options
.open(path)
.with_context(|| format!("Failed to open {}", path.display()))?;
let metadata = file
.metadata()
.with_context(|| format!("Failed to inspect {}", path.display()))?;
if !metadata.is_file() {
anyhow::bail!("{label} must be a regular file");
}
let read_limit = max_bytes
.checked_add(1)
.context("bounded input byte limit is too large")?;
let mut content = Vec::new();
file.take(read_limit)
.read_to_end(&mut content)
.with_context(|| format!("Failed to read {}", path.display()))?;
if content.len() as u64 > max_bytes {
anyhow::bail!("{label} exceeds the {max_bytes}-byte limit");
}
Ok(content)
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()View on GitHub (pinned to 8321021c54)