{"record":{"id":"317c01ca4d771544","repo":"jdx/mise","slug":"not-a-directory","errorCode":null,"errorMessage":"not a directory: {}","messagePattern":"not a directory: (.+?)","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/oci/layer.rs","lineNumber":100,"sourceCode":"        return Err(format!(\"{name} must not be empty\"));\n    }\n    value\n        .parse::<u32>()\n        .map_err(|_| format!(\"{name} must be a non-negative integer <= {}\", u32::MAX))\n}\n\n/// Build a reproducible gzipped tar layer from files in `src_dir`, placing them\n/// under `target_prefix` inside the tar (e.g. `/mise/installs/node/20.0.0`).\n///\n/// `src_dir` must exist and be a directory. Symlinks are preserved; their\n/// targets are NOT followed. `owner` is applied to every emitted tar entry.\npub fn build_layer_from_dir(\n    src_dir: &Path,\n    target_prefix: &str,\n    owner: LayerOwner,\n) -> Result<LayerBlob> {\n    if !src_dir.is_dir() {\n        eyre::bail!(\"not a directory: {}\", src_dir.display());\n    }\n\n    let entries = collect_sorted_entries(src_dir, false, owner)?;\n    build_layer_from_entries(&entries, target_prefix, owner)\n}\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 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","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/oci/layer.rs#L82-L118","documentation":"build_layer_from_dir requires src_dir to exist and be a directory; the guard fails fast before walking entries. Layers built from directories (tool installs, dotfile dirs) call this with the on-disk install root, so the error means the expected directory is missing or is not a directory.","triggerScenarios":"A [copy]/layer source path pointing at a regular file or a nonexistent path where a directory is required; a tool's install directory missing because the tool was never installed before `mise oci build` (the OCI builder consumes already-installed versions).","commonSituations":"Running mise oci build on a clean machine without `mise install` first; a typo'd or moved source path in config; a symlink-to-file passed where the caller expects a directory.","solutions":["Run `mise install` (or `mise use -f`) so every tool version referenced exists under the installs directory, then rebuild.","Verify the source path with `ls -ld <path>`; fix the config or create the directory.","If the source is intentionally a single file, point the entry at the file with an exact destination file name instead of expecting directory semantics."],"exampleFix":"# before\nmise oci build          # tools never installed\n\n# after\nmise install && mise oci build","handlingStrategy":"validation","validationCode":"import os, sys\nfor d in required_source_dirs:\n    if not os.path.isdir(d):\n        sys.exit(f\"missing source directory: {d}; run `mise install` first\")","typeGuard":"fn is_usable_source_dir(p: &Path) -> bool {\n    p.is_dir() // symlink to dir also true; files/fifos/missing are false\n}","tryCatchPattern":null,"preventionTips":["Run `mise install` before `mise oci build` in fresh environments (CI: cache or explicit install step).","Pin the exact tool versions in mise.toml so installs are reproducible before layering.","Check `ls -ld` on configured source paths when adding new copy/dotfile entries."],"tags":["oci","layer","filesystem","missing-directory"],"backgroundTag":"invalid-source-path","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}