tauri-apps/tauri · error · anyhow::Error

{:?} is not a file

Error message

{:?} is not a file

What it means

The sibling check in tauri-build's copy_file: the source path exists but is not a regular file (typically a directory), while the copy routines here need a concrete file. The Debug-formatted path in the message identifies the entry that resolved to a directory.

Source

Thrown at crates/tauri-build/src/lib.rs:50

mod codegen;
mod manifest;
mod mobile;
mod static_vcruntime;

#[cfg(feature = "codegen")]
#[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
pub use codegen::context::CodegenContext;

pub use acl::{AppManifest, DefaultPermissionRule, InlinedPlugin};

fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
  let from = from.as_ref();
  let to = to.as_ref();
  if !from.exists() {
    return Err(anyhow::anyhow!("{:?} does not exist", from));
  }
  if !from.is_file() {
    return Err(anyhow::anyhow!("{:?} is not a file", from));
  }
  let dest_dir = to.parent().expect("No data in parent");
  fs::create_dir_all(dest_dir)?;
  fs::copy(from, to)?;
  Ok(())
}

fn copy_binaries(
  binaries: ResourcePaths,
  target_triple: &str,
  path: &Path,
  package_name: Option<&str>,
) -> Result<()> {
  for src in binaries {
    let src = src?;
    println!("cargo:rerun-if-changed={}", src.display());
    let file_name = src
      .file_name()

View on GitHub (pinned to 2f1cd75b0f)

Solutions

  1. Point the config entry at a concrete file path instead of its containing directory
  2. For macOS frameworks use a .framework path (copied as a directory by design) or a bare name resolved from standard locations - not an arbitrary directory
  3. If multiple assets are intended, use the bundle.resources glob/map forms the config supports rather than one directory path

Example fix

// before
"externalBin": ["binaries/"]

// after
"externalBin": ["binaries/mytool-x86_64-unknown-linux-gnu"]
Defensive patterns

Strategy: validation

Validate before calling

// prebuild check: every externalBin entry must be a regular file
const { statSync } = require('fs')
const cfg = JSON.parse(readFileSync('src-tauri/tauri.conf.json', 'utf8'))
for (const b of cfg.bundle?.externalBin ?? []) {
  const s = statSync(`src-tauri/${b}`, { throwIfNoEntry: false })
  if (s && !s.isFile()) throw new Error(`externalBin entry is not a file: ${b}`)
}

Prevention

When it happens

Trigger: A bundle.externalBin entry or a .dylib framework entry (anything funneled through copy_file) resolving to a directory - e.g. pointing externalBin at the folder containing sidecar binaries, or listing an assets directory where a single file is required.

Common situations: Confusing a directory of assets or binaries with one file in tauri.conf.json; glob/map expansion producing the parent directory; naming a macOS framework bundle directory where a .dylib file path is expected.

Related errors


AI-assisted analysis of tauri-apps/tauri@2f1cd75b0f (2026-08-16). Data as JSON: /api/errors/f6e037c8d5fb6a90. Report an issue: GitHub.