oven-sh/bun · error · BuildError

Failed to exec ${exe}

Error message

Failed to exec ${exe}

What it means

scripts/build.ts spawns the just-linked binary with spawnSync and throws this BuildError when the spawn itself errors (child.error set) — the binary could not be launched at all, before any code inside it ran. The original error (ENOENT, EACCES, etc.) is attached as cause.

Source

Thrown at scripts/build.ts:299

      // it's not obvious ninja finished vs. stalled. This disambiguates.
      // Targets named when explicit so it's clear what was actually built.
      const what = args.ninjaTargets.length > 0 ? ` ${args.ninjaTargets.map(t => nameColor(t)).join(", ")}` : "";
      status(`[build]${what} done`);
      process.exit(0);
    }

    // Exec the built binary. result.output.exe is the linked (unstripped)
    // binary — bun-debug for debug, bun-profile for release. That's the one
    // you want for dev iteration (has symbols + assertions in debug).
    const exe = result.output.exe;
    if (exe === undefined) {
      throw new BuildError("Cannot exec: build mode produced no executable", {
        hint: `mode=${result.cfg.mode} builds artifacts, not a runnable binary. Drop the positional args or use --profile=debug.`,
      });
    }
    const child = spawnSync(exe, args.execArgs, { stdio: "inherit" });
    if (child.error) {
      throw new BuildError(`Failed to exec ${exe}`, { cause: child.error });
    }
    // Signal death: re-raise so our parent sees the same signal (shells
    // show "Segmentation fault" etc. based on this, not exit code).
    if (child.signal) {
      process.kill(process.pid, child.signal);
      return;
    }
    process.exit(child.status ?? 0);
  }
}

/**
 * When an HTTP proxy is configured, cargo's `-Zbuild-std` (release lolhtml)
 * must reach crates.io. Some CI/corporate proxies 403 CONNECT to package
 * registries while direct egress is open. If a proxy is set and crates.io
 * isn't already exempted, probe direct connectivity once: if it works, add
 * crates.io to NO_PROXY so cargo goes direct. If the probe fails (mandatory-
 * egress-proxy topology, firewall drops direct), leave NO_PROXY untouched so

View on GitHub (pinned to 8c5296ac45)

Solutions

  1. Check the exe path in the message exists and is executable (ls -l)
  2. Rebuild cleanly: remove the build dir and run bun bd again
  3. Fix permissions or move the repo off a filesystem that strips the exec bit
  4. Verify the profile matches the host (no cross-compile profile combined with exec args)
Defensive patterns

Strategy: try-catch

Validate before calling

import { existsSync, accessSync, constants } from "node:fs";
// after the build resolves but before exec
if (!existsSync(exe)) throw new Error(`build output missing: ${exe}`);
accessSync(exe, constants.X_OK); // throws if not executable

Try / catch

try {
  const child = spawnSync(exe, execArgs, { stdio: "inherit" });
  if (child.error) throw new BuildError(`Failed to exec ${exe}`, { cause: child.error });
} catch (e) {
  const code = e.cause?.code;
  if (code === "ENOENT") { /* rebuild then retry once */ }
  else if (code === "EACCES") { /* fix the exec bit / filesystem */ }
  else throw e;
}

Prevention

When it happens

Trigger: ENOENT because result.output.exe points to a path that was not produced or was removed (partial build, pruned build dir), EACCES/EPERM from a missing execute bit, or the OS refusing to run the binary (loader mismatch, quarantine).

Common situations: Build dir cleaned or moved between link and exec; disk full so linking truncated the binary; filesystems that strip the exec bit (network mounts, WSL interop); a cross-compiled binary that does not match the host.

Related errors


AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16). Data as JSON: /api/errors/b82c2be9fd86cceb. Report an issue: GitHub.