BoundaryML/baml · critical

failed to exec baml-cli: {err}

Error message

failed to exec baml-cli: {err}

What it means

On Unix, the `baml` wrapper hands control to the resolved `baml-cli` binary via `Command::exec()`, which only returns on failure. If the exec syscall fails (permission denied, missing binary, bad interpreter, ENOEXEC), the wrapper throws this error wrapping the underlying io::Error.

Source

Thrown at baml_language/crates/baml/src/main.rs:454

    // too would double it up.
    let channel_warned = warn_if_channel_outdated(&selector, &version);
    // A cache refresh (due at most once per TTL window) runs in the
    // background while the command itself runs, instead of stalling it.
    let refresh = start_lazy_refresh(&selector);

    let mut command = Command::new(cli);
    command.args(args);
    command.env("BAML_WRAPPER_EXEC", "1");
    command.env("BAML_WRAPPER_RESOLVED_TOOLCHAIN", &version);

    let Some(refresh) = refresh else {
        // Common case: nothing to refresh, so the wrapper can hand the
        // process over entirely.
        #[cfg(unix)]
        {
            use std::os::unix::process::CommandExt;
            let err = command.exec();
            return Err(anyhow!("failed to exec baml-cli: {err}"));
        }
        #[cfg(not(unix))]
        {
            let status = command.status().context("failed to run baml-cli")?;
            return Ok(status.code().unwrap_or(1));
        }
    };

    // Refresh in flight: run the command as a child (exec would kill the
    // refresh thread), then give the refresh whatever remains of its budget
    // and surface any warnings the fresh caches newly justify.
    let status = command.status().context("failed to run baml-cli")?;
    refresh.wait();
    if !channel_warned {
        warn_if_channel_outdated(&selector, &version);
    }
    Ok(status.code().unwrap_or(1))
}

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Run `baml self-update` (or reinstall baml) to restore a healthy baml-cli binary.
  2. Check the exec bit on the resolved binary: `chmod +x $(which baml-cli)` or the toolchain path shown in the error.
  3. Verify the binary's filesystem is not mounted noexec: `mount | grep <path>`; remount with exec or relocate the toolchain.
  4. Inspect the wrapped io::Error text in the message (e.g. ENOENT vs EACCES) to target the specific cause.

Example fix

# before (exec fails: missing exec bit)
$ baml generate
error: failed to exec baml-cli: Permission denied (os error 13)

# after
$ chmod +x ~/.baml/toolchains/0.210.0/bin/baml-cli
$ baml generate
Defensive patterns

Strategy: try-catch

Validate before calling

import os, shutil, stat
def can_exec_cli():
    path = shutil.which("baml-cli")
    if path is None:
        raise SystemExit("baml-cli not found — run `baml self-update` or reinstall")
    if not (os.stat(path).st_mode & stat.S_IXUSR):
        raise SystemExit(f"baml-cli not executable: {path} — chmod +x")

Type guard

function isExecutableFile(p: string): boolean {
  try { fs.accessSync(p, fs.constants.X_OK); return fs.statSync(p).isFile(); }
  catch { return false; }
}

Try / catch

result = subprocess.run(["baml"] + args, capture_output=True)
if result.returncode != 0 and "failed to exec baml-cli" in result.stderr.decode():
    # wrapper-level exec failure: reinstall before retrying
    subprocess.run(["baml", "self-update"], check=True)

Prevention

When it happens

Trigger: Calling any pass-through `baml <args>` command where the resolved baml-cli binary cannot be exec'd: file not present despite stale state, non-executable permissions, corrupted install, or a filesystem mounted noexec.

Common situations: Interrupted self-update leaving a partial baml-cli, toolchain directory moved/deleted after `use`, copying the install onto a noexec mount (e.g. some /tmp mounts or network shares), or antivirus/quarantine stripping the exec bit.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/9a48c8564a6cb530. Report an issue: GitHub.