jdx/mise · error

[dotfiles]."{req.target_raw}": mode symlink-each requires a

Error message

[dotfiles]."{req.target_raw}": mode symlink-each requires a directory source: {req.source.display}

What it means

The `symlink-each` dotfile mode creates one symlink per child entry of the source, so the source must be a directory. If the source is a regular file (or other non-directory), mise rejects the entry because per-entry symlinking is undefined for a single file.

Source

Thrown at src/oci/builder.rs:950

                "[dotfiles].\"{}\": source does not exist: {}",
                req.target_raw,
                req.source.display()
            );
        }

        match req.mode {
            // a tracked file lives on the machine that tracks it; an image
            // has nothing to copy
            FileMode::Track => continue,
            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 afd2eddd3a)

Solutions

  1. Change the mode to `symlink` or `copy` for single-file sources.
  2. Point `source` at the directory whose children should each be symlinked.
  3. Split the entry: use `symlink-each` for the directory and a separate `symlink`/`content` entry for individual files.

Example fix

# before
[dotfiles."/root/.config"]
source = "~/.gitconfig"
mode = "symlink-each"
# after
[dotfiles."/root/.gitconfig"]
source = "~/.gitconfig"
mode = "symlink"
Defensive patterns

Strategy: validation

Validate before calling

if (req.mode === 'symlink-each' && !fs.statSync(req.source).isDirectory()) {
  throw new Error(`symlink-each requires a directory source: ${req.source}`);
}

Type guard

const isDir = (p) => { try { return fs.statSync(p).isDirectory(); } catch { return false; } };

Prevention

When it happens

Trigger: `[dotfiles]."target"` entry with `mode = "symlink-each"` whose `source` points at a file instead of a directory — commonly after changing a directory source into a single file, or a typo pointing at a sibling file.

Common situations: Pointing symlink-each at `~/.config/foo.conf` intending to symlink it; refactoring a dotfiles dir into individual files while keeping the old mode; a symlink-each source that is itself a symlink to a file.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/ebaac092374524ad. Report an issue: GitHub.