jdx/mise · error
cannot copy a file or symlink to the image root
Error message
cannot copy a file or symlink to the image root
What it means
`build_layer_from_path` copies a host file or symlink into the image at the given image path. If the destination trims to the empty string (image root, e.g. image_path "/"), it refuses — a file cannot be placed at the container root without a name. Directories are handled earlier via `build_layer_from_dir`.
Solutions
- Set the image path to the full destination file name, e.g. "/etc/foo.conf" instead of "/".
- If the intent was to copy directory contents, ensure the host path is a directory (the directory branch handles trailing '/').
- Validate copy specs before building: reject empty trimmed targets for file/symlink sources.
Example fix
// before
build_layer_from_path("./app.conf", "/", owner)?; // error
// after
build_layer_from_path("./app.conf", "/etc/app.conf", owner)?; Defensive patterns
Strategy: validation
Validate before calling
fn validate_copy_spec(host: &Path, image_path: &str) -> Result<()> {
let trimmed = image_path.trim_matches('/');
if !host.is_dir() && trimmed.is_empty() {
bail!("file copy spec needs a concrete image path, got {image_path:?}");
}
Ok(())
} Try / catch
match build_layer_from_path(host, image_path, owner) {
Err(e) if e.to_string().contains("cannot copy a file or symlink to the image root") => {
bail!("give the file a full destination path like /etc/app.conf")
}
r => r,
} Prevention
- Never use "/" as the image path for file/symlink copies.
- Lint copy specs so file sources always include a file name.
- Check templated image paths after substitution for degenerate "/" values.
When it happens
Trigger: Calling `build_layer_from_path` with a non-directory host path and an image_path of "/", "", or "//" so `trim_matches('/')` yields an empty target.
Common situations: Config mistake where a copy rule intended to place a file into a directory instead targets the root; templated image_path evaluated to "/"; copy spec written as "/" for a single file.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- copy image path ends with `/` but is not a directory…
- not a directory
- copy image path ends with `/` but is not a directory…
- [dotfiles]: duplicate OCI path
- : expected exactly one manifest in index.json
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/dfc862a08a85fb34.
Report an issue: GitHub.
Appendix: source
Thrown at src/oci/layer.rs:174
}
/// Build a reproducible layer from one host file, symlink, or directory.
/// 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());View on GitHub (pinned to 533346cc37)