{"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":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/files.rs","lineNumber":1574,"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":1556,"sourceCodeEnd":1592,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/files.rs#L1556-L1592","documentation":"check_symlink_each validates that an entry with `mode = \"symlink-each\"` has a directory as its source, since symlink-each links every file inside the source directory individually. When the source is missing or a regular file, it bails; callers surface this in the status row or apply error list.","triggerScenarios":"Applying a dotfile entry with `mode = \"symlink-each\"` whose `source` does not exist or points at a file, e.g. `{ source = \"dotfiles/single.conf\", mode = \"symlink-each\" }` or a source directory that was deleted/renamed.","commonSituations":"Dotfiles repo reorganized so the source folder no longer exists; typo in the source path; intending per-file linking but pointing at a single file; source not yet cloned on a fresh machine.","solutions":["Point `source` at the directory containing the files you want individually linked.","If linking a single file, switch to `mode = \"symlink\"` or `mode = \"copy\"`.","Ensure the dotfiles repository is cloned/checked out so the source directory exists."],"exampleFix":"// before\n\"~/.config/app\" = { source = \"dotfiles/app.conf\", mode = \"symlink-each\" }\n// after\n\"~/.config/app\" = { source = \"dotfiles/app\", mode = \"symlink-each\" }","handlingStrategy":"validation","validationCode":"let src = shellexpand(source);\nif !src.is_dir() {\n    return Err(format!(\"symlink-each source must be an existing directory: {src:?}\"));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Check that the dotfiles repo is cloned before applying on a new machine.","Use symlink-each only for directories; symlink/copy for single files.","Re-check source paths after reorganizing the dotfiles repository."],"tags":["dotfiles","symlink","filesystem","mise"],"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"}