jdx/mise · error

cannot write Windows launcher because {} is not a generated

Error message

cannot write Windows launcher because {} is not a generated launcher

What it means

Next to every stub it writes, mise also writes a Windows launcher `<stub>.cmd` (on every OS, since stubs are committed for Windows contributors). That launcher path already holds a regular file that is not a mise-generated launcher (which must be exactly `@echo off`, `rem generated by mise`, one command line ending in ` %*`). Because bin/<task>.cmd is a name a project may already use for its own batch script, mise refuses to clobber it and stops the whole run during validation, before writing anything.

Source

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

        }
    }
    Ok(migrations.into_iter().collect())
}

/// Refuse to replace a `.cmd` beside a stub that mise did not write.
///
/// The stub path is the user's choice, so `bin/<task>.cmd` is a name a project may already be
/// using for a script of its own — and unlike the stub itself, nothing about the name says mise
/// owns it. Checked during validation rather than at the write, so a launcher that is not ours
/// stops the whole run instead of leaving a half-generated `bin/`.
fn validate_launcher_path(stub: &TaskStub<'_>) -> Result<()> {
    let Some(launcher) = super::windows_launcher_path(&stub.path) else {
        return Ok(());
    };
    match fs::symlink_metadata(&launcher) {
        Ok(metadata) if metadata.file_type().is_file() => {
            if !super::is_generated_launcher(&file::read_to_string(&launcher)?) {
                bail!(
                    "cannot write Windows launcher because {} is not a generated launcher",
                    display_path(&launcher)
                );
            }
        }
        Ok(_) => bail!(
            "cannot write Windows launcher because {} is not a regular file",
            display_path(&launcher)
        ),
        Err(err) if matches!(err.kind(), ErrorKind::NotFound | ErrorKind::NotADirectory) => {}
        Err(err) => return Err(err.into()),
    }
    Ok(())
}

fn validate_generated_stub_directory(path: &Path, expected: &str, task: &Task) -> Result<()> {
    let default = path.join("_default");
    match fs::symlink_metadata(&default) {

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. If the .cmd is the project's own, rename it (`git mv bin/task.cmd bin/task-run.cmd`) or port its logic into the task definition
  2. If it is a stale/disposable launcher, delete it: `git rm bin/<task>.cmd`
  3. Pick different task names (or --dir) so the launcher paths do not collide

Example fix

# before: bin/build.cmd is a hand-written wrapper
$ mise generate task-stubs
# error: cannot write Windows launcher because bin/build.cmd is not a generated launcher

# after
$ git mv bin/build.cmd scripts/build.cmd
$ mise generate task-stubs
Defensive patterns

Strategy: type-guard

Validate before calling

#!/bin/bash
# any existing .cmd next to a stub target must be mise-generated
for f in bin/**/*.cmd bin/*.cmd; do
  [ -f "$f" ] || continue
  is_mise_launcher "$f" || { echo "$f is not a generated launcher" >&2; exit 1; }
done
mise generate task-stubs

Type guard

is_mise_launcher() {
  [ "$(head -1 "$1")" = '@echo off' ] && [ "$(sed -n 2p "$1")" = 'rem generated by mise' ]
}

Try / catch

if ! mise generate task-stubs; then
  echo 'a .cmd beside a stub is hand-written — rename it or fold its logic into the task' >&2
  exit 1
fi

Prevention

When it happens

Trigger: `mise generate task-stubs` on any OS when bin/<task>.cmd is a hand-written batch file; a previously generated launcher was edited until it no longer matched the generated shape.

Common situations: Windows-friendly repo that already ships bin/<name>.cmd wrappers; adopting task-stubs where cmd scripts exist for the same task names.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/bd989b5647f7089e. Report an issue: GitHub.