{"record":{"id":"e303b82793bf145e","repo":"jdx/mise","slug":"mode-symlink-each-requires-the-source-to-be-a-dire","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":null,"httpStatus":null,"severity":"error","filePath":"src/system/files.rs","lineNumber":1374,"sourceCode":"        }\n    } else if target.exists() {\n        Ok(FileState::Differs(\"exists but is not a symlink\".into()))\n    } else {\n        Ok(FileState::Missing)\n    }\n}\n\nfn points_at_same_file(target: &Path, source: &Path) -> bool {\n    match (target.canonicalize(), source.canonicalize()) {\n        (Ok(a), Ok(b)) => a == b,\n        _ => false,\n    }\n}\n\nfn check_symlink_each(req: &FileRequest) -> Result<FileState> {\n    if !req.source.is_dir() {\n        // callers add the entry's context (status row / apply error list)\n        bail!(\n            \"mode symlink-each requires the source to be a directory: {}\",\n            req.source.display_user()\n        );\n    }\n    let stale = stale_links(req)?;\n    let stale_reason = || format!(\"{} stale link(s)\", stale.len());\n    let files = walk_source_files(req)?;\n    // with no files to link the desired state is just the target directory —\n    // a blocking non-directory must still surface (and be --force-able)\n    if files.is_empty() {\n        return if req.target.is_dir() {\n            if stale.is_empty() {\n                Ok(FileState::Applied)\n            } else {\n                Ok(FileState::Differs(stale_reason()))\n            }\n        } else if req.target.exists() || req.target.is_symlink() {\n            Ok(FileState::Differs(\"exists but is not a directory\".into()))","sourceCodeStart":1356,"sourceCodeEnd":1392,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/files.rs#L1356-L1392","documentation":"`mode = \"symlink-each\"` links each entry inside the source directory individually into the target directory, so the source must be a directory. `check_symlink_each` validates this before computing stale links and fails with the offending source path; callers wrap the error with the entry's status row or apply-error context.","triggerScenarios":"Declaring `mode = \"symlink-each\"` for a dotfile whose `source` (or implied source) resolves to a regular file or does not exist when `mise dotfiles` status/apply runs `check_symlink_each`.","commonSituations":"Pointing symlink-each at a single file (meant plain symlink mode), the source file missing after renaming something in the dotfiles repo, or an implied source resolving to the wrong path because the target nesting differs from the repo layout.","solutions":["Point `source` at the directory you want mirrored (e.g. `source = \"config/app\"` not `config/app/app.conf`).","If the source is genuinely a single file, drop `symlink-each` and use the default symlink or copy mode.","Create the missing source directory in the dotfiles root, or fix the target so the implied source resolves to an existing directory."],"exampleFix":"// before\n[dotfiles.\"~/.config/app\"]\nmode = \"symlink-each\"\nsource = \"app/app.conf\"   # a file\n\n// after\n[dotfiles.\"~/.config/app\"]\nmode = \"symlink-each\"\nsource = \"app\"            # the directory","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nfunction assertSourceIsDir(sourcePath) {\n  if (!fs.statSync(sourcePath).isDirectory())\n    throw new Error(`mode symlink-each requires the source to be a directory: ${sourcePath}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  applyDotfiles();\n} catch (e) {\n  if (/symlink-each requires the source to be a directory/.test(e.message)) {\n    console.error('Point source at a directory, or use plain symlink/copy mode for a single file');\n  } else throw e;\n}","preventionTips":["Only use symlink-each for directory sources; audit entries after renaming paths in the dotfiles repo","Verify the source path exists before committing a new symlink-each entry","Run dotfiles status after repo restructuring to catch file/dir mismatches early"],"tags":["dotfiles","symlink-each","path-validation","mise"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}