jdx/mise · error

exposes no executable scripts

Error message

{} exposes no executable scripts

What it means

After installing the uv-locked environment, install_uv_lock runs a Python discovery script that lists the console/gui scripts and script files exposed by the requested packages. If the resulting set is empty, the tool provides nothing to symlink into bin/, so mise fails the install rather than producing an install with no usable executables.

Solutions

  1. Verify the PyPI package actually ships a CLI; if it's a library, don't install it as a tool (use it as a dependency instead).
  2. Run `mise lock --bump <tool>` to rebuild the lock with the correct root package and version.
  3. Check the package's metadata for console_scripts entry points (e.g. inspect the wheel's entry_points.txt).
  4. If the CLI moved to a differently-named distribution, point the tool at the correct PyPI name.

Example fix

// before: library without entry points installed as a tool
[tools]
someclilib = "pipx:someclilib"  # no console_scripts

// after: use the actual CLI distribution
[tools]
someclilib = "pipx:somecli"
Defensive patterns

Strategy: validation

Validate before calling

// confirm the package ships console scripts before treating it as a CLI tool
const eps = wheelMetadata('entry_points.txt');
if (!/\[console_scripts\]/.test(eps)) console.warn('package exposes no CLI');

Prevention

When it happens

Trigger: Thrown from install_uv_lock (called by install_version_) when the JSON returned by the DISCOVER_SCRIPTS python snippet deserializes to an empty list — i.e. none of the locked packages expose console_scripts/gui_scripts entry points or script files in the venv scripts directory.

Common situations: Locking/installing a library-only PyPI package (no CLI) as a tool; all entry points gated behind inactive environment markers; the wrong package name expected by importlib.metadata (dependency split/renamed); installing via `pipx:` when the CLI actually lives in a different distribution.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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

Appendix: source

Thrown at src/backend/pipx/lock.rs:494

            .execute()?;
        let scripts = project
            .join(".venv")
            .join(if cfg!(windows) { "Scripts" } else { "bin" });
        let python = scripts.join(if cfg!(windows) {
            "python.exe"
        } else {
            "python"
        });
        let packages = self.locked_entry_point_packages(tv, lock)?;
        let packages = serde_json::to_string(&packages)?;
        let names = CmdLineRunner::new(python)
            .args(["-I", "-c", DISCOVER_SCRIPTS, &packages])
            .arg(&scripts)
            .read()
            .await?;
        let names: Vec<String> = serde_json::from_str(names.trim())?;
        if names.is_empty() {
            bail!("{} exposes no executable scripts", self.ba.short);
        }
        let bin = tv.install_path().join("bin");
        crate::file::create_dir_all(&bin)?;
        for name in names {
            if !crate::file::is_plain_file_name(&name) {
                bail!("invalid Python entry point name");
            }
            crate::file::make_symlink_or_copy(&scripts.join(&name), &bin.join(&name))?;
        }
        Ok(())
    }

    fn locked_entry_point_packages(&self, tv: &ToolVersion, lock: &UvLock) -> Result<Vec<String>> {
        let root_requirement = lock
            .project
            .get("project")
            .and_then(toml::Value::as_table)
            .and_then(|project| project.get("dependencies"))

View on GitHub (pinned to 533346cc37)