jdx/mise · error · eyre::Report
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 trims leading/trailing slashes from image_path; for a file or symlink source the trimmed target must be non-empty because the entry is placed at that exact path. A destination of "/" or "//" trims to "", so mise refuses rather than writing a file at the image root.
Source
Thrown at src/oci/layer.rs:123
}
/// 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 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 9dcfcaa0dc)
Solutions
- Give the file a concrete destination, e.g. "/usr/local/bin/mytool" or "/etc/motd".
- If you meant to copy a whole tree to the root, make the source a directory (directories are allowed to target '/').
Example fix
# before files = [ "./bin/mytool:/" ] # after files = [ "./bin/mytool:/usr/local/bin/mytool" ]
Defensive patterns
Strategy: validation
Validate before calling
import os, sys
for src, dst in copy_entries.items():
if not os.path.isdir(src) and dst.strip('/') == '':
sys.exit(f"file source {src} needs a full destination path, got {dst!r}") Prevention
- Always spell the complete destination file path for file/symlink sources.
- Reserve '/' destinations for directory sources only.
- Lint copy entries in CI for empty-or-slash destinations on non-directory sources.
When it happens
Trigger: A copy-style entry whose destination is "/" (or any all-slash string) while the source is a regular file or symlink.
Common situations: Thinking "/" means 'copy into the root directory' (which would need a directory source plus a name); template-generated destinations that collapse to empty and get a default '/' appended.
Related errors
- oci mount_point must not be empty
- oci mount_point must be an absolute path (got {mount_point:?
- [dotfiles]."{}": target is not a safe OCI path
- not a directory: {}
- copy image path {image_path:?} ends with `/` but {} is not a
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/dfc862a08a85fb34.
Report an issue: GitHub.