jdx/mise · error

copy image path {image_path:?} ends with `/` but {host_path.

Error message

copy image path {image_path:?} ends with `/` but {host_path.display} is not a directory; specify the exact destination file name

What it means

In build_layer_from_path, a trailing '/' on the image path signals directory-copy semantics, so when the host path is a plain file (or symlink) the destination ending in '/' is contradictory. This error tells the caller to specify the exact destination file name instead of a directory-style path.

Source

Thrown at src/oci/layer.rs:174

/// Directory contents are placed under `image_path`; a file or symlink is
/// placed at `image_path` itself.
pub(crate) fn build_layer_from_path(
    host_path: &Path,
    image_path: &str,
    owner: LayerOwner,
) -> Result<LayerBlob> {
    let metadata = std::fs::symlink_metadata(host_path)
        .wrap_err_with(|| format!("reading metadata for {}", host_path.display()))?;
    let target = image_path.trim_matches('/');

    if metadata.is_dir() {
        return build_layer_from_dir(host_path, target, owner);
    }
    if target.is_empty() {
        eyre::bail!("cannot copy a file or symlink to the image root");
    }
    if image_path.ends_with('/') {
        eyre::bail!(
            "copy image path {image_path:?} ends with `/` but {} is not a directory; \
             specify the exact destination file name",
            host_path.display()
        );
    }

    let kind = if metadata.file_type().is_symlink() {
        EntryKind::Symlink(
            std::fs::read_link(host_path)
                .wrap_err_with(|| format!("reading symlink {}", host_path.display()))?,
        )
    } else if metadata.is_file() {
        EntryKind::File
    } else {
        eyre::bail!("unsupported host path type: {}", host_path.display());
    };
    let mode = match &kind {
        EntryKind::Symlink(_) => 0o777,

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Remove the trailing slash and give the full destination file path: dest = "/etc/app/app.conf".
  2. If directory semantics are wanted, point src at a directory so build_layer_from_dir runs instead.
  3. Adjust templates/generators that append '/' to every destination regardless of whether src is a file.

Example fix

// before
{ src = "./app.conf", dest = "/etc/app/" }
// after
{ src = "./app.conf", dest = "/etc/app/app.conf" }
Defensive patterns

Strategy: validation

Validate before calling

function assertDestMatchesSrcKind(src, dest) {
  const isDir = fs.statSync(src).isDirectory();
  if (!isDir && dest.endsWith("/")) {
    throw new Error(`file src needs exact file dest, not dir-style: ${dest}`);
  }
}

Try / catch

match build_layer_from_path(&host, image_path, owner) {
    Err(e) if e.to_string().contains("ends with `/`") => {
        eprintln!("drop trailing slash and name the file: {image_path} -> {image_path}/{file_name}");
    }
    r => r,
}

Prevention

When it happens

Trigger: Calling build_layer_from_path with a non-directory host_path and image_path with a trailing slash, e.g. { src = "app.conf", dest = "/etc/app/" } — the file branch is taken, image_path.ends_with('/') is true, and the bail fires.

Common situations: Copy-pasting directory-style destinations from another tool (docker COPY semantics); templated dest built by joining a dir prefix plus '/' for a file entry; user expecting the file to be renamed automatically into the directory.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/4e3a53486d33f4f1. Report an issue: GitHub.