jdx/mise · error

cannot replace task stub directory because {} is empty

Error message

cannot replace task stub directory because {} is empty

What it means

When replacing a generated stub directory, mise recursively validates its tree. Every subdirectory must contain at least one recognized file (a generated stub, or only mise launchers which count as empty). A subdirectory with no children at all, or one holding only launcher files so it has zero actual stub files, is treated as not a generated tree and mise refuses to proceed.

Source

Thrown at src/cli/generate/task_stubs.rs:636

            display_path(path),
            task.display_name
        ),
    }

    validate_generated_stub_tree(path, launchers)?;
    Ok(())
}

fn validate_generated_stub_tree(path: &Path, launchers: &Launchers) -> Result<usize> {
    let mut files = 0;
    for entry in fs::read_dir(path)? {
        let entry = entry?;
        let entry_path = entry.path();
        let metadata = fs::symlink_metadata(&entry_path)?;
        if metadata.file_type().is_dir() {
            let child_files = validate_generated_stub_tree(&entry_path, launchers)?;
            if child_files == 0 {
                bail!(
                    "cannot replace task stub directory because {} is empty",
                    display_path(&entry_path)
                );
            }
            files += child_files;
        } else if metadata.file_type().is_file() && is_exe_path(&entry_path) {
            // A native launcher of ours. Checked before anything reads the file as text, which a
            // binary is not. Not counted towards `files`, same as the `.cmd`: a directory holding
            // nothing but launchers has no stubs left and should still be reported as empty.
            if !launchers.owns(&entry_path) {
                bail!(
                    "cannot replace task stub directory because {} is not a generated task stub",
                    display_path(&entry_path)
                );
            }
        } else if metadata.file_type().is_file()
            && is_generated_task_stub(&file::read_to_string(&entry_path)?)
        {

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Delete the empty subdirectory (and any launcher-only directories), then re-run `mise generate task-stubs`.
  2. Back up anything you want to keep from the stub directory tree and remove the whole directory to let mise regenerate.
  3. Investigate why stubs vanished (partial deletion, sync conflict) before regenerating.

Example fix

// before: .mise/tasks/build/legacy/ is an empty dir
rm -rf .mise/tasks/build/legacy
mise generate task-stubs
// after: directory replaced cleanly
Defensive patterns

Strategy: validation

Validate before calling

for sub in Path('.mise/tasks/build').rglob('*'):
    if sub.is_dir() and not any(sub.iterdir()):
        print(f"empty subdirectory {sub} will block stub regeneration")

Type guard

def no_empty_subdirs(d: Path) -> bool:
    return all(any(sub.iterdir()) for sub in d.rglob('*') if sub.is_dir())

Prevention

When it happens

Trigger: Running `mise generate task-stubs` when a subdirectory inside an existing stub directory is empty, or contains only `.cmd`/native launchers with no generated stub files left.

Common situations: User deleted stub files but left empty directories behind; a failed/partial cleanup removed stubs but not launchers; copying the tasks tree with an incomplete sync tool.

Related errors


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