{"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":null,"httpStatus":null,"severity":"error","filePath":"src/oci/builder.rs","lineNumber":1006,"sourceCode":"                \"[dotfiles].\\\"{}\\\": source does not exist: {}\",\n                req.target_raw,\n                req.source.display()\n            );\n        }\n\n        match req.mode {\n            // a tracked file lives on the machine that tracks it; an image\n            // has nothing to copy\n            FileMode::Track => continue,\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":988,"sourceCodeEnd":1024,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/oci/builder.rs#L988-L1024","documentation":"Mode symlink-each creates one symlink per entry inside the source directory, so the source must be a directory. If a [dotfiles] entry uses symlink-each but its source resolves to a regular file (or symlink to a file), the builder rejects it before writing any layer entries.","triggerScenarios":"build (via build_dotfiles_layer) with a [dotfiles] entry like mode = \"symlink-each\" whose source points at a single file instead of a directory of dotfiles.","commonSituations":"Copy-pasting a symlink-each config block for a single-file dotfile like .vimrc; the source was a directory when written but was later replaced by a file; confusing symlink-each with the plain symlink mode.","solutions":["Point source at the directory containing the individual dotfiles (e.g. /home/me/dotfiles).","For a single file, change the mode to \"symlink\" (or \"content\"/\"track\") instead of \"symlink-each\".","Verify with `ls -la` that the source path is a directory on the build host."],"exampleFix":"// before\n[dotfiles.\"~/.vimrc\"]\nmode = \"symlink-each\"\nsource = \"/home/me/.vimrc\"\n\n// after\n[dotfiles.\"~/.vimrc\"]\nmode = \"symlink\"\nsource = \"/home/me/.vimrc\"","handlingStrategy":"validation","validationCode":"# symlink-each requires a directory source\n[ -d \"$HOME/dotfiles\" ] || { echo \"symlink-each source must be a directory\" >&2; exit 1; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use symlink-each only for directories of dotfiles; use symlink/content for single files.","Don't replace a directory source with a single file without updating the mode.","Check `ls -la` on source paths when editing [dotfiles] config."],"tags":["oci","dotfiles","config","type-mismatch"],"backgroundTag":"path-is-not-a-directory","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"}