jdx/mise · error
mode symlink-each requires the source to be a directory
Error message
mode symlink-each requires the source to be a directory: {} What it means
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.
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.
Example fix
// before
"~/.config/app" = { source = "dotfiles/app.conf", mode = "symlink-each" }
// after
"~/.config/app" = { source = "dotfiles/app", mode = "symlink-each" } Defensive patterns
Strategy: validation
Validate before calling
let src = shellexpand(source);
if !src.is_dir() {
return Err(format!("symlink-each source must be an existing directory: {src:?}"));
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to list Git-tracked files in
- failed to locate Git repository for
- a parent directory is now a symlink; left untouched
- binary target is not an owned Caskroom symlink
- brew-cask: binary artifact
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/e303b82793bf145e.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/files.rs:1574
}
} else if target.exists() {
Ok(FileState::Differs("exists but is not a symlink".into()))
} else {
Ok(FileState::Missing)
}
}
fn points_at_same_file(target: &Path, source: &Path) -> bool {
match (target.canonicalize(), source.canonicalize()) {
(Ok(a), Ok(b)) => a == b,
_ => false,
}
}
fn check_symlink_each(req: &FileRequest) -> Result<FileState> {
if !req.source.is_dir() {
// callers add the entry's context (status row / apply error list)
bail!(
"mode symlink-each requires the source to be a directory: {}",
req.source.display_user()
);
}
let stale = stale_links(req)?;
let stale_reason = || format!("{} stale link(s)", stale.len());
let files = walk_source_files(req)?;
// with no files to link the desired state is just the target directory —
// a blocking non-directory must still surface (and be --force-able)
if files.is_empty() {
return if req.target.is_dir() {
if stale.is_empty() {
Ok(FileState::Applied)
} else {
Ok(FileState::Differs(stale_reason()))
}
} else if req.target.exists() || req.target.is_symlink() {
Ok(FileState::Differs("exists but is not a directory".into()))View on GitHub (pinned to 533346cc37)