vercel/turborepo · error · LocalTurboProcess

Failed to execute local `turbo` process.

Error message

Failed to execute local `turbo` process.

What it means

The global turbo shim resolved the repo-local turbo binary and tried to spawn it via runtime.child_spawner.spawn(command) (run.rs:524 region); the OS-level spawn failed and is wrapped as Error::LocalTurboProcess with the io::Error preserved as #[source] (run.rs:102-103). The message is intentionally generic — the real cause (ENOENT, EACCES, EMFILE, missing cwd…) is in the chained source error.

Source

Thrown at crates/turborepo-shim/src/run.rs:524

    raw_args.append(&mut shim_args.forwarded_args);

    raw_args
}

fn spawn_child_turbo<R, C, S, V>(
    runtime: &ShimRuntime<R, C, S, V>,
    command: process::Command,
    err: fn(std::io::Error) -> Error,
) -> ShimResult<R::Error>
where
    R: TurboRunner,
    C: ConfigProvider,
    S: ChildSpawner,
    V: VersionProvider,
{
    let child: Arc<SharedChild> = match runtime.child_spawner.spawn(command) {
        Ok(child) => child,
        Err(e) => return ShimResult::ShimError(err(e)),
    };

    // The child turbo process shares our process group and will receive
    // SIGINT directly from the kernel when the user presses Ctrl+C.
    // Ignore SIGINT in the shim *after* spawning so the child inherits
    // the default disposition (and can register its own handler), while
    // the shim stays alive to collect the child's exit status.
    #[cfg(unix)]
    unsafe {
        libc::signal(libc::SIGINT, libc::SIG_IGN);
    }

    let exit_status = match child.wait() {
        Ok(status) => status,
        Err(e) => return ShimResult::ShimError(err(e)),
    };
    let exit_code = exit_status.code().unwrap_or_else(|| {
        debug!("child turbo failed to report exit code");

View on GitHub (pinned to f9245100cf)

Solutions

  1. Reinstall dependencies (`pnpm install` / `npm install`) to regenerate node_modules/.bin/turbo
  2. Inspect the source error: run `ls -l node_modules/.bin/turbo*` and try executing it directly to see the errno
  3. If EACCES on Linux/WSL: `chmod +x` the binary, or mount the repo with metadata enabled
  4. Install turbo as a direct devDependency so the local binary always exists

Example fix

# before
ls node_modules/.bin/turbo        # missing or not executable
# after
pnpm install
chmod +x node_modules/.bin/turbo  # if needed (WSL/DrvFs)
node_modules/.bin/turbo --version
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight the local binary before handing off to the shim
let bin = repo.join("node_modules/.bin/turbo");
#[cfg(unix)]
{ use std::os::unix::fs::PermissionsExt; assert!(fs::metadata(&bin)?.permissions().mode() & 0o111 != 0); }

Type guard

fn spawnable(p: &Path) -> bool { p.is_file() && fs::metadata(p).map(|m| m.permissions().readonly() == false).unwrap_or(false) }

Try / catch

// the io::Error source carries the errno — branch on it
match run() {
    Err(Error::LocalTurboProcess(source)) => match source.kind() {
        std::io::ErrorKind::NotFound => reinstall_deps(),
        std::io::ErrorKind::PermissionDenied => fix_exec_bit(),
        _ => return Err(source.into()),
    },
    r => r?,
}

Prevention

When it happens

Trigger: Spawning node_modules/.bin/turbo (or the resolved local turbo path) fails: the binary vanished between detection and spawn (ENOENT), is not executable / has a broken shebang (EACCES/ENOEXEC), or the process hit fd/memory limits (EMFILE/ENOMEM).

Common situations: Interrupted or partially-applied package-manager install leaving a broken .bin entry Repo copied from Windows/another machine losing the exec bit (WSL DrvFs, chmod issues) Corrupt npx/pnpm-managed shims after a Node version switch

Related errors


AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17). Data as JSON: /api/errors/697a954db4f48711. Report an issue: GitHub.