jdx/mise · error · eyre::Report
[dotfiles]."{}": mode symlink-each requires a directory sour
Error message
[dotfiles]."{}": mode symlink-each requires a directory source: {} What it means
An `[oci.dotfiles]` entry with `mode = "symlink-each"` symlinks every entry inside a source directory into the image (one symlink per file, preserving names). Because it iterates directory contents with walkdir, the source must be a directory; pointing symlink-each at a regular file is a configuration error and aborts the build with the entry key and path.
Source
Thrown at src/oci/builder.rs:882
for req in requests {
if req.mode != FileMode::Content && !req.source.exists() {
bail!(
"[dotfiles].\"{}\": source does not exist: {}",
req.target_raw,
req.source.display()
);
}
match req.mode {
FileMode::Symlink | FileMode::Copy => {
collect_source_as_files(&req.source, &oci_target_path(req)?, &mut entries)
.wrap_err_with(|| {
format!("adding [dotfiles].\"{}\" to OCI image", req.target_raw)
})?;
}
FileMode::SymlinkEach => {
if !req.source.is_dir() {
bail!(
"[dotfiles].\"{}\": mode symlink-each requires a directory source: {}",
req.target_raw,
req.source.display()
);
}
let target = oci_target_path(req)?;
entries.add_dir(target.clone())?;
for entry in walkdir::WalkDir::new(&req.source).sort_by_file_name() {
let entry = entry?;
let ft = entry.file_type();
if !(ft.is_file() || ft.is_symlink()) {
continue;
}
let rel = entry.path().strip_prefix(&req.source)?;
let path = format!("{target}/{}", rel.to_string_lossy().replace('\\', "/"));
entries.add_file(
path,
file::read(entry.path())?,View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Point the symlink-each entry at a directory containing the files you want individually linked
- If you meant to link a single file, use `mode = "symlink"` (or "copy") instead
- Restructure the source so each linked item sits inside a dedicated directory
Example fix
# before (mise.toml)
[oci.dotfiles]
"~/bin" = { source = "./run-tool.sh", mode = "symlink-each" }
# after (mise.toml)
"~/bin/run-tool.sh" = { source = "./run-tool.sh", mode = "symlink" } Defensive patterns
Strategy: validation
Validate before calling
python3 - <<'EOF'
import tomllib, os, sys
cfg = tomllib.load(open('mise.toml', 'rb'))
for target, spec in (cfg.get('oci', {}).get('dotfiles', {}) or {}).items():
if isinstance(spec, dict) and spec.get('mode') == 'symlink-each':
src = os.path.expanduser(spec.get('source', ''))
if not os.path.isdir(src): sys.exit(f'dotfiles {target!r}: symlink-each source is not a directory: {src}')
print('symlink-each sources OK')
EOF Prevention
- Remember the mode distinction: symlink = one link to the source; symlink-each = one link per entry inside a directory
- Prefer symlink (single file) unless you truly need per-entry links
- Validate dotfiles config before running oci build
When it happens
Trigger: `[oci.dotfiles]` entry with mode symlink-each whose source resolves to a file, e.g. `"bin" = { source = "./script.sh", mode = "symlink-each" }`. Also triggered when a expected directory path is occupied by a same-named file.
Common situations: Confusing symlink (whole target) with symlink-each (per-entry inside a directory); restructuring a dotfiles repo so a former directory is now a file.
Understand the failure class
Background: Config validation failed: what "invalid value for {key}" and settings-rejection errors mean across 19 open-source libraries — this error's family across 19 libraries.
Related errors
- [dotfiles]."{}": source does not exist: {}
- [dotfiles]: duplicate OCI path {path:?} as both file and dir
- oci mount_point must not be empty
- oci mount_point must be an absolute path (got {mount_point:?
- [dotfiles]: duplicate OCI file path {path:?}
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/91d9ff516d3e7475.
Report an issue: GitHub.