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

  1. Point the argument at an actual regular file, not a directory or special file.
  2. Verify the path exists and is a file with `ls -l` / `file <path>` before invoking.
  3. 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

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


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)