jdx/mise · error

[dotfiles]." ": mode symlink-each requires a directory…

Error message

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

What it means

Mode symlink-each creates one symlink per entry inside the source directory, so the source must be a directory. If a [dotfiles] entry uses symlink-each but its source resolves to a regular file (or symlink to a file), the builder rejects it before writing any layer entries.

Solutions

  1. Point source at the directory containing the individual dotfiles (e.g. /home/me/dotfiles).
  2. For a single file, change the mode to "symlink" (or "content"/"track") instead of "symlink-each".
  3. Verify with `ls -la` that the source path is a directory on the build host.

Example fix

// before
[dotfiles."~/.vimrc"]
mode = "symlink-each"
source = "/home/me/.vimrc"

// after
[dotfiles."~/.vimrc"]
mode = "symlink"
source = "/home/me/.vimrc"
Defensive patterns

Strategy: validation

Validate before calling

# symlink-each requires a directory source
[ -d "$HOME/dotfiles" ] || { echo "symlink-each source must be a directory" >&2; exit 1; }

Prevention

When it happens

Trigger: build (via build_dotfiles_layer) with a [dotfiles] entry like mode = "symlink-each" whose source points at a single file instead of a directory of dotfiles.

Common situations: Copy-pasting a symlink-each config block for a single-file dotfile like .vimrc; the source was a directory when written but was later replaced by a file; confusing symlink-each with the plain symlink mode.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/91d9ff516d3e7475. Report an issue: GitHub.

Appendix: source

Thrown at src/oci/builder.rs:1006

                "[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 533346cc37)