{"record":{"id":"91d9ff516d3e7475","repo":"jdx/mise","slug":"dotfiles-mode-symlink-each-requires-a-dire","errorCode":null,"errorMessage":"[dotfiles].\"{}\": mode symlink-each requires a directory source: {}","messagePattern":"\\[dotfiles\\]\\.\"(.+?)\": mode symlink-each requires a directory source: (.+?)","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/oci/builder.rs","lineNumber":882,"sourceCode":"    for req in requests {\n        if req.mode != FileMode::Content && !req.source.exists() {\n            bail!(\n                \"[dotfiles].\\\"{}\\\": source does not exist: {}\",\n                req.target_raw,\n                req.source.display()\n            );\n        }\n\n        match req.mode {\n            FileMode::Symlink | FileMode::Copy => {\n                collect_source_as_files(&req.source, &oci_target_path(req)?, &mut entries)\n                    .wrap_err_with(|| {\n                        format!(\"adding [dotfiles].\\\"{}\\\" to OCI image\", req.target_raw)\n                    })?;\n            }\n            FileMode::SymlinkEach => {\n                if !req.source.is_dir() {\n                    bail!(\n                        \"[dotfiles].\\\"{}\\\": mode symlink-each requires a directory source: {}\",\n                        req.target_raw,\n                        req.source.display()\n                    );\n                }\n                let target = oci_target_path(req)?;\n                entries.add_dir(target.clone())?;\n                for entry in walkdir::WalkDir::new(&req.source).sort_by_file_name() {\n                    let entry = entry?;\n                    let ft = entry.file_type();\n                    if !(ft.is_file() || ft.is_symlink()) {\n                        continue;\n                    }\n                    let rel = entry.path().strip_prefix(&req.source)?;\n                    let path = format!(\"{target}/{}\", rel.to_string_lossy().replace('\\\\', \"/\"));\n                    entries.add_file(\n                        path,\n                        file::read(entry.path())?,","sourceCodeStart":864,"sourceCodeEnd":900,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/oci/builder.rs#L864-L900","documentation":"An `[oci.dotfiles]` entry with `mode = \"symlink-each\"` symlinks every entry inside a source directory into the image (one symlink per file, preserving names). Because it iterates directory contents with walkdir, the source must be a directory; pointing symlink-each at a regular file is a configuration error and aborts the build with the entry key and path.","triggerScenarios":"`[oci.dotfiles]` entry with mode symlink-each whose source resolves to a file, e.g. `\"bin\" = { source = \"./script.sh\", mode = \"symlink-each\" }`. Also triggered when a expected directory path is occupied by a same-named file.","commonSituations":"Confusing symlink (whole target) with symlink-each (per-entry inside a directory); restructuring a dotfiles repo so a former directory is now a file.","solutions":["Point the symlink-each entry at a directory containing the files you want individually linked","If you meant to link a single file, use `mode = \"symlink\"` (or \"copy\") instead","Restructure the source so each linked item sits inside a dedicated directory"],"exampleFix":"# before (mise.toml)\n[oci.dotfiles]\n\"~/bin\" = { source = \"./run-tool.sh\", mode = \"symlink-each\" }\n\n# after (mise.toml)\n\"~/bin/run-tool.sh\" = { source = \"./run-tool.sh\", mode = \"symlink\" }","handlingStrategy":"validation","validationCode":"python3 - <<'EOF'\nimport tomllib, os, sys\ncfg = tomllib.load(open('mise.toml', 'rb'))\nfor target, spec in (cfg.get('oci', {}).get('dotfiles', {}) or {}).items():\n    if isinstance(spec, dict) and spec.get('mode') == 'symlink-each':\n        src = os.path.expanduser(spec.get('source', ''))\n        if not os.path.isdir(src): sys.exit(f'dotfiles {target!r}: symlink-each source is not a directory: {src}')\nprint('symlink-each sources OK')\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember the mode distinction: symlink = one link to the source; symlink-each = one link per entry inside a directory","Prefer symlink (single file) unless you truly need per-entry links","Validate dotfiles config before running oci build"],"tags":["oci","dotfiles","build","mode-mismatch","config"],"backgroundTag":"config-validation-failed","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}