{"record":{"id":"2424040208a51ce3","repo":"rust-lang/mdBook","slug":"expected-a-file-appears-to-be","errorCode":null,"errorMessage":"expected a file, `{}` appears to be {:?}","messagePattern":"expected a file, `(.+?)` appears to be (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/mdbook-core/src/utils/fs.rs","lineNumber":179,"sourceCode":"    // This is a workaround for an issue with the macOS file watcher.\n    // Rust's `std::fs::copy` function uses `fclonefileat`, which creates\n    // clones on APFS. Unfortunately fs events seem to trigger on both\n    // sides of the clone, and there doesn't seem to be a way to differentiate\n    // which side it is.\n    // https://github.com/notify-rs/notify/issues/465#issuecomment-1657261035\n    // contains more information.\n    //\n    // This is essentially a copy of the simple copy code path in Rust's\n    // standard library.\n    #[cfg(target_os = \"macos\")]\n    fn copy_inner(from: &Path, to: &Path) -> Result<()> {\n        use std::fs::OpenOptions;\n        use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};\n\n        let mut reader = std::fs::File::open(from)?;\n        let metadata = reader.metadata()?;\n        if !metadata.is_file() {\n            anyhow::bail!(\n                \"expected a file, `{}` appears to be {:?}\",\n                from.display(),\n                metadata.file_type()\n            );\n        }\n        let perm = metadata.permissions();\n        let mut writer = OpenOptions::new()\n            .mode(perm.mode())\n            .write(true)\n            .create(true)\n            .truncate(true)\n            .open(to)?;\n        let writer_metadata = writer.metadata()?;\n        if writer_metadata.is_file() {\n            // Set the correct file permissions, in case the file already existed.\n            // Don't set the permissions on already existing non-files like\n            // pipes/FIFOs or device nodes.\n            writer.set_permissions(perm)?;","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/rust-lang/mdBook/blob/dc21064fc21d955a0e1f67e65e336883c3e5260b/crates/mdbook-core/src/utils/fs.rs#L161-L197","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nutils::fs::copy(&src_path, &dest_path)?; // src_path is a directory\n// after\nif src_path.is_dir() {\n    utils::fs::copy_files(&src_path, &dest_path)?;\n} else {\n    utils::fs::copy(&src_path, &dest_path)?;\n}","handlingStrategy":"validation","validationCode":"fn ensure_regular_file(p: &Path) -> std::io::Result<()> {\n    let md = std::fs::metadata(p)?;\n    if md.is_file() { Ok(()) } else {\n        Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!(\"{} is not a file\", p.display())))\n    }\n}","typeGuard":"fn is_regular_file(p: &Path) -> bool {\n    std::fs::metadata(p).map(|m| m.is_file()).unwrap_or(false)\n}","tryCatchPattern":"match utils::fs::copy(from, to) {\n    Ok(()) => (),\n    Err(e) if e.to_string().contains(\"expected a file\") =>\n        // fall back to recursive copy for directories\n        utils::fs::copy_files(from, to)?,\n    Err(e) => return Err(e),\n}","preventionTips":["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"],"tags":["filesystem","io"],"backgroundTag":"expected-file-found-directory","analyzedSha":"dc21064fc21d955a0e1f67e65e336883c3e5260b","analyzedAt":"2026-09-01T10:15:12.134Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}