{"record":{"id":"dfc862a08a85fb34","repo":"jdx/mise","slug":"cannot-copy-a-file-or-symlink-to-the-image-root","errorCode":null,"errorMessage":"cannot copy a file or symlink to the image root","messagePattern":"cannot copy a file or symlink to the image root","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/oci/layer.rs","lineNumber":174,"sourceCode":"}\n\n/// Build a reproducible layer from one host file, symlink, or directory.\n/// Directory contents are placed under `image_path`; a file or symlink is\n/// placed at `image_path` itself.\npub(crate) fn build_layer_from_path(\n    host_path: &Path,\n    image_path: &str,\n    owner: LayerOwner,\n) -> Result<LayerBlob> {\n    let metadata = std::fs::symlink_metadata(host_path)\n        .wrap_err_with(|| format!(\"reading metadata for {}\", host_path.display()))?;\n    let target = image_path.trim_matches('/');\n\n    if metadata.is_dir() {\n        return build_layer_from_dir(host_path, target, owner);\n    }\n    if target.is_empty() {\n        eyre::bail!(\"cannot copy a file or symlink to the image root\");\n    }\n    if image_path.ends_with('/') {\n        eyre::bail!(\n            \"copy image path {image_path:?} ends with `/` but {} is not a directory; \\\n             specify the exact destination file name\",\n            host_path.display()\n        );\n    }\n\n    let kind = if metadata.file_type().is_symlink() {\n        EntryKind::Symlink(\n            std::fs::read_link(host_path)\n                .wrap_err_with(|| format!(\"reading symlink {}\", host_path.display()))?,\n        )\n    } else if metadata.is_file() {\n        EntryKind::File\n    } else {\n        eyre::bail!(\"unsupported host path type: {}\", host_path.display());","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/oci/layer.rs#L156-L192","documentation":"`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`.","triggerScenarios":"Calling `build_layer_from_path` with a non-directory host path and an image_path of \"/\", \"\", or \"//\" so `trim_matches('/')` yields an empty target.","commonSituations":"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.","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."],"exampleFix":"// before\nbuild_layer_from_path(\"./app.conf\", \"/\", owner)?; // error\n\n// after\nbuild_layer_from_path(\"./app.conf\", \"/etc/app.conf\", owner)?;","handlingStrategy":"validation","validationCode":"fn validate_copy_spec(host: &Path, image_path: &str) -> Result<()> {\n    let trimmed = image_path.trim_matches('/');\n    if !host.is_dir() && trimmed.is_empty() {\n        bail!(\"file copy spec needs a concrete image path, got {image_path:?}\");\n    }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"match build_layer_from_path(host, image_path, owner) {\n    Err(e) if e.to_string().contains(\"cannot copy a file or symlink to the image root\") => {\n        bail!(\"give the file a full destination path like /etc/app.conf\")\n    }\n    r => r,\n}","preventionTips":["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."],"tags":["oci","layer","copy","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}