rust-lang/mdBook · error
expected a file, `{}` appears to be {:?}
Error message
expected a file, `{}` appears to be {:?} What it means
mdbook's copy helper (utils::fs::copy / copy_inner) opens the source with File::open and requires it to be a regular file. If the source path is a directory, fifo, socket, or other non-file, it bails with this message. It exists because the copy implementation streams file contents and assumes a regular file.
Source
Thrown at crates/mdbook-core/src/utils/fs.rs:179
// This is a workaround for an issue with the macOS file watcher.
// Rust's `std::fs::copy` function uses `fclonefileat`, which creates
// clones on APFS. Unfortunately fs events seem to trigger on both
// sides of the clone, and there doesn't seem to be a way to differentiate
// which side it is.
// https://github.com/notify-rs/notify/issues/465#issuecomment-1657261035
// contains more information.
//
// This is essentially a copy of the simple copy code path in Rust's
// standard library.
#[cfg(target_os = "macos")]
fn copy_inner(from: &Path, to: &Path) -> Result<()> {
use std::fs::OpenOptions;
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
let mut reader = std::fs::File::open(from)?;
let metadata = reader.metadata()?;
if !metadata.is_file() {
anyhow::bail!(
"expected a file, `{}` appears to be {:?}",
from.display(),
metadata.file_type()
);
}
let perm = metadata.permissions();
let mut writer = OpenOptions::new()
.mode(perm.mode())
.write(true)
.create(true)
.truncate(true)
.open(to)?;
let writer_metadata = writer.metadata()?;
if writer_metadata.is_file() {
// Set the correct file permissions, in case the file already existed.
// Don't set the permissions on already existing non-files like
// pipes/FIFOs or device nodes.
writer.set_permissions(perm)?;View on GitHub (pinned to dc21064fc2)
Solutions
- Ensure the source path points to a regular file
- Use utils::fs::copy_files (recursive) instead of copy() when copying directories
- Remove or rename directories inside the src/theme directories that shouldn't be copied
Example fix
// before
utils::fs::copy(&src_path, &dest_path)?; // src_path is a directory
// after
if src_path.is_dir() {
utils::fs::copy_files(&src_path, &dest_path)?;
} else {
utils::fs::copy(&src_path, &dest_path)?;
} Defensive patterns
Strategy: validation
Validate before calling
fn ensure_regular_file(p: &Path) -> std::io::Result<()> {
let md = std::fs::metadata(p)?;
if md.is_file() { Ok(()) } else {
Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{} is not a file", p.display())))
}
} Type guard
fn is_regular_file(p: &Path) -> bool {
std::fs::metadata(p).map(|m| m.is_file()).unwrap_or(false)
} Try / catch
match utils::fs::copy(from, to) {
Ok(()) => (),
Err(e) if e.to_string().contains("expected a file") =>
// fall back to recursive copy for directories
utils::fs::copy_files(from, to)?,
Err(e) => return Err(e),
} Prevention
- Check source.is_file() before copying
- Use copy_files for directories, copy for files only
- Avoid directories under src/ or theme/ that shouldn't be treated as assets
When it happens
Trigger: Passing a directory (or special file) as the source path to utils::fs::copy; mdbook internals copying a src item that is actually a directory (often from a bad `src` layout or symlink target).
Common situations: Custom renderers/preprocessors that place directories under the src or theme directory and then trigger a copy; users pointing `book.src` or theme paths at directories.
Related errors
- theme dir {} does not exist
- ignore root canonicalize error
- One of the paths should be an absolute
- ignore root canonicalize error
- One of the paths should be an absolute
AI-assisted analysis of rust-lang/mdBook@dc21064fc2 (2026-09-01).
Data as JSON: /api/errors/2424040208a51ce3.
Report an issue: GitHub.