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
`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.
Source
Thrown at src/system/files.rs:1374
}
} 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 afd2eddd3a)
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.
Example fix
// before [dotfiles."~/.config/app"] mode = "symlink-each" source = "app/app.conf" # a file // after [dotfiles."~/.config/app"] mode = "symlink-each" source = "app" # the directory
Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs');
function assertSourceIsDir(sourcePath) {
if (!fs.statSync(sourcePath).isDirectory())
throw new Error(`mode symlink-each requires the source to be a directory: ${sourcePath}`);
} Try / catch
try {
applyDotfiles();
} catch (e) {
if (/symlink-each requires the source to be a directory/.test(e.message)) {
console.error('Point source at a directory, or use plain symlink/copy mode for a single file');
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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`.
Common situations: 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.
Related errors
- dotfile target must be absolute or start with ~/: {target}
- files: cannot unapply these entries:\n{}
- brew-cask: invalid completion target '{}'
- brew-cask: invalid binary target '{}'
- brew-cask:{}: invalid {kind} run command path {}
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/e303b82793bf145e.
Report an issue: GitHub.