jdx/mise · error

cannot replace task stub directory because

Error message

cannot replace task stub directory because {} is not a generated task stub

What it means

During stub-tree validation, mise treats executable-extension files as native launchers it generated, and confirms ownership via `Launchers::owns`. If an `.exe`/executable-named file in the stub directory was not created by mise (not in its launcher registry), mise refuses to replace the directory with this error.

Solutions

  1. Move the foreign executable out of the stub directory
  2. Delete the stale/unowned launcher file
  3. Point `mise generate task-stub` at a dedicated output directory that only contains generated stubs

Example fix

// before: user's own tool.exe sits in stubs\
stubs\tool.exe  # not owned by mise -> bail
// after
mv stubs\tool.exe .\tools\
mise generate task-stub --output stubs
Defensive patterns

Strategy: validation

Validate before calling

const foreignExe = fs.readdirSync(stubDir)
  .filter(f => /\.(exe|cmd|bat)$/i.test(f))
  .filter(f => !generatedLauncherRegistry.includes(f));

Try / catch

try {
  await exec('mise generate task-stub ...');
} catch (e) {
  if (String(e).includes('is not a generated task stub')) {
    // move foreign executables out of stubDir, then retry
  }
}

Prevention

When it happens

Trigger: Running `mise generate task-stub` against a directory containing a file with an executable extension (e.g. `.exe`) that is not tracked as a mise-generated launcher — a user-placed binary or a stale launcher from a removed output path.

Common situations: Users copied real tools/binaries into the stub directory; launcher registry reset (path moved) so previously generated launchers are no longer owned; shared bin directory pointed at as output.

Related errors


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

Appendix: source

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

    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)?)
        {
            files += 1;
        } else if metadata.file_type().is_file()
            && super::is_generated_launcher(&file::read_to_string(&entry_path)?)
        {
            // Our own Windows launcher. Not counted towards `files`: a directory holding nothing
            // but launchers has no stubs left and should still be reported as empty.
        } else {
            bail!(
                "cannot replace task stub directory because {} is not a generated task stub",
                display_path(&entry_path)
            );

View on GitHub (pinned to 533346cc37)