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
- Remove the trailing slash and give the full destination file path: dest = "/etc/app/app.conf".
- If directory semantics are wanted, point src at a directory so build_layer_from_dir runs instead.
- 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
- Strip trailing slashes from generated destinations for file entries.
- Only add trailing slash when src is a directory.
- Document copy semantics (docker COPY-style) in your config schema.
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
- cannot copy a file or symlink to the image root
- oci mount_point must be an absolute path (got {mount_point:?
- not a directory: {src_dir.display}
- unsupported host path type: {host_path.display}
- mise oci needs `apk` on PATH to install apk system packages
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/4e3a53486d33f4f1.
Report an issue: GitHub.