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 soView on GitHub (pinned to 8c5296ac45)
Solutions
- Check the exe path in the message exists and is executable (ls -l)
- Rebuild cleanly: remove the build dir and run bun bd again
- Fix permissions or move the repo off a filesystem that strips the exec bit
- 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
- Do not delete or move build-dir artifacts while a build-then-exec command runs
- On unusual filesystems, verify the exec bit after linking
- Treat ENOENT on a fresh binary as rebuild-needed, not a runtime bug
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
- Failed to spawn pwsh
- Failed to load config file: ${path}
- ${bunProfile} not found — build it first (bun run build:rele
- file did not exist after write: ${outputPath}
- not implemented right
AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16).
Data as JSON: /api/errors/b82c2be9fd86cceb.
Report an issue: GitHub.