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

  1. Give the file a concrete destination, e.g. "/usr/local/bin/mytool" or "/etc/motd".
  2. 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

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


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/dfc862a08a85fb34. Report an issue: GitHub.