jdx/mise · error

cannot replace task stub directory because

Error message

cannot replace task stub directory because {} is empty

What it means

When validating a stub directory tree before replacement, mise recursively counts generated stub files. If a subdirectory contains no countable generated stubs at all (it is empty or holds only launchers, which are not counted), mise bails with this error rather than deleting an empty directory during replacement.

Solutions

  1. Delete the empty subdirectory before re-running generation
  2. Place a valid generated task stub in the subdirectory
  3. Regenerate into a fresh output directory

Example fix

// before: stubs\nested\ exists but is empty
mkdir -p stubs\nested
mise generate task-stub --output stubs   # fails
// after
rmdir stubs\nested
mise generate task-stub --output stubs
Defensive patterns

Strategy: validation

Validate before calling

const hasEmptySubdir = (dir: string): boolean =>
  fs.readdirSync(dir, { withFileTypes: true, recursive: true })
    .some(d => d.isDirectory() && fs.readdirSync(path.join(d.parentPath ?? dir, d.name)).length === 0);

Try / catch

try {
  await exec('mise generate task-stub ...');
} catch (e) {
  if (String(e).includes('is empty')) {
    // remove empty subdirectories then retry
    await exec('find ' + stubDir + ' -type d -empty -delete');
    await exec('mise generate task-stub ...');
  }
}

Prevention

When it happens

Trigger: Running `mise generate task-stub` against an output directory containing an empty subdirectory, or a subdirectory whose only files are Windows launchers/`.cmd` files (explicitly excluded from the stub count).

Common situations: User manually cleaned out a subfolder of stubs but left the folder; a previous partial generation left a launcher-only directory; git checkout or sync tooling pruned stub files but kept directories.

Related errors


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

Appendix: 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 533346cc37)