{"record":{"id":"40369e65825d9dcd","repo":"jdx/mise","slug":"mode-symlink-each-requires-the-source-to-be-a-dire-40369e","errorCode":null,"errorMessage":"mode symlink-each requires the source to be a directory","messagePattern":"mode symlink-each requires the source to be a directory","errorType":"validation","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/system/files.rs","lineNumber":1599,"sourceCode":"                bail!(\"source directory is missing, so managed children cannot be identified\");\n            }\n            plan_single_file(req, opts, &mut paths)?;\n        }\n        FileMode::Content => {\n            if !req.target.exists() && !req.target.is_symlink() {\n                return Ok(None);\n            }\n            if opts.force {\n                paths.insert(req.target.clone(), ());\n            } else {\n                plan_inline_file(req, &mut paths)?;\n            }\n        }\n        FileMode::Symlink => {\n            plan_single_file(req, opts, &mut paths)?;\n        }\n        FileMode::SymlinkEach => {\n            bail!(\"mode symlink-each requires the source to be a directory\");\n        }\n        FileMode::Template => {\n            if !req.target.exists() && !req.target.is_symlink() {\n                return Ok(None);\n            }\n            if opts.force {\n                paths.insert(req.target.clone(), ());\n            } else if opts.dry_run {\n                // Rendering may execute commands. Keep dry-run inert, matching\n                // apply/status policy, and describe the removal as conditional.\n                paths.insert(req.target.clone(), ());\n                conditional = true;\n            } else {\n                if !req.source.exists() {\n                    bail!(\"source is missing; use --force to remove the target\");\n                }\n                // Rendering may execute user-authored commands. Defer it until\n                // after the complete unapply plan has been confirmed.","sourceCodeStart":1581,"sourceCodeEnd":1617,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/files.rs#L1581-L1617","documentation":"`symlink-each` only supports directory sources: mise recreates the source tree under the target and symlinks each contained file individually. This terminal match arm fires when a symlink-each entry is planned and its source is not a directory — in practice on Windows (the earlier non-Windows arm intercepts first) or when `source` names a regular file or is missing.","triggerScenarios":"A `[dotfiles]` entry with `mode = \"symlink-each\"` whose `source` is a file or does not exist, planned on Windows (`cfg!(windows)`), because on Unix the `FileMode::SymlinkEach if !cfg!(windows)` arm matches first.","commonSituations":"Copy-pasted entry pointing at a single file instead of its parent directory; source dir renamed; a cross-platform mise.toml used on Windows where the guard sends planning to this arm.","solutions":["Point `source` at the directory containing the files to link","For single files, use `mode = \"symlink\"` or `mode = \"copy\"` instead","Remove the entry if it is obsolete"],"exampleFix":"# mise.toml - before: symlink-each on a single file\n[[dotfiles]]\nsource = \"~/dotfiles/ssh/config\"\ntarget = \"~/.ssh/config\"\nmode = \"symlink-each\"\n\n# after: link the single file directly\n[[dotfiles]]\nsource = \"~/dotfiles/ssh/config\"\ntarget = \"~/.ssh/config\"\nmode = \"symlink\"\n\n# or link every file in the directory:\n# source = \"~/dotfiles/ssh\", target = \"~/.ssh\", mode = \"symlink-each\"","handlingStrategy":"validation","validationCode":"# Lint: every symlink-each entry's source must be a directory.\npython3 - <<'EOF'\nimport tomllib, pathlib\nfor f in pathlib.Path('.').rglob('mise.toml'):\n    for e in tomllib.loads(f.read_text()).get('dotfiles', []):\n        if e.get('mode') == 'symlink-each':\n            s = pathlib.Path(e['source']).expanduser()\n            assert s.is_dir(), f\"{f}: symlink-each source not a dir: {s}\"\nEOF","typeGuard":"fn is_symlink_each_bad_source(err: &miette::Report) -> bool {\n    err.to_string().contains(\n        \"mode symlink-each requires the source to be a directory\",\n    )\n}","tryCatchPattern":null,"preventionTips":["Reserve `symlink-each` for directories; use `symlink` for single files","Lint mise.toml in CI after editing dotfile entries","Test cross-platform configs on both OSes before committing"],"tags":["mise","dotfiles","symlink-each","windows","config"],"backgroundTag":"invalid-config-value","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T20:17:18.057Z"}