jdx/mise · error

[dotfiles]."{}": manifest requires the source to be a direct

Error message

[dotfiles]."{}": manifest requires the source to be a directory: {}

What it means

validate_composed_file_footprints enforces that a manifest-backed dotfile declaration owns a directory as its source, since the manifest enumerates the files inside it. A non-Track request that has a manifest but whose source path exists and is not a directory is rejected with this error; Track requests are skipped because they own no apply leaf.

Source

Thrown at src/system/files.rs:416

}

/// Validate the complete paths claimed by composed `[dotfiles]` entries.
/// Directory copies and `symlink-each` entries may share directories, but no
/// two entries may own the same leaf or require a directory where another
/// entry places a leaf.
pub(crate) fn validate_composed_file_footprints(requests: &[FileRequest]) -> Result<()> {
    let mut leaves: IndexMap<PathBuf, &FileRequest> = IndexMap::new();
    let mut directories: IndexMap<PathBuf, &FileRequest> = IndexMap::new();
    let mut symlink_each_identities: HashMap<(&Path, &Path), &FileRequest> = HashMap::new();

    for request in requests {
        // Tracking observes native files; it does not own an apply leaf.
        // A tracked parent may contain independently managed destinations.
        if request.mode == FileMode::Track {
            continue;
        }
        if request.manifest.is_some() && request.source.exists() && !request.source.is_dir() {
            bail!(
                "[dotfiles].\"{}\": manifest requires the source to be a directory: {}",
                request.target_raw,
                request.source.display_user()
            );
        }
        if request.mode == FileMode::SymlinkEach
            && let Some(existing) = symlink_each_identities.insert(
                (request.source.as_path(), request.target.as_path()),
                request,
            )
        {
            return Err(composed_file_footprint_conflict(
                &request.target,
                existing,
                request,
            ));
        }
        // A missing source has an unknown eventual shape, but it still claims

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Point the declaration's source at the directory that contains the files listed in the manifest.
  2. Remove the manifest key if you intend to manage a single file directly.
  3. Recreate the directory layout the manifest expects (move the file into the expected directory).

Example fix

# before: manifest with a file source
[dotfiles."~/.config"]
source = "~/dotfiles/config.toml"
manifest = "manifest.toml"

# after: source is the directory
[dotfiles."~/.config"]
source = "~/dotfiles"
manifest = "manifest.toml"
Defensive patterns

Strategy: validation

Validate before calling

# manifest sources must be directories
p=$(mise get dotfile-source ~/.config); [ -d "$p" ] || echo "not a directory: $p"

Type guard

const manifestSourceIsDir = (p: string) => fs.existsSync(p) && fs.statSync(p).isDirectory();

Prevention

When it happens

Trigger: Calling any of the plan/validate entry points (plan_apply_with_active, the composed_symlink_each_* validators, tracking footprint checks) with a FileRequest where manifest.is_some(), source.exists(), and !source.is_dir() — i.e. a [dotfiles] entry points at a regular file while declaring a manifest.

Common situations: The user points a manifest dotfile at a single file instead of the directory containing it (e.g. source = "~/dotfiles/gitconfig" with a manifest, instead of source = "~/dotfiles"), or the source was replaced by a file after being a directory.

Related errors


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