Replace the npm wrapper with the native binary using execve (#57513)

## Why

Replacing the Node process lets the native binary receive signals and report its termination directly.

## What changed

- Use `process.execve` on non-Windows platforms when available, preserving CLI arguments and the prepared environment.
- Install a `SIGXFSZ` handler before replacement so the native binary gets the default signal disposition instead of inheriting Node's ignored signal.
- Retain asynchronous spawning for Windows and older Node versions.

## Testing

Extend the exit-status test to check that `execve` leaves no child process and that `SIGHUP` and `SIGXFSZ` terminate it with the original signal, while retaining coverage for the spawn fallback.

GitOrigin-RevId: 7437f15af419fce5b10c1c00425c7d38160991fc
This commit is contained in:
Michael Bolin
2026-10-27 16:55:48 +00:00
committed by copyberry
parent 5a361b5596
commit 27ace641fe
2 changed files with 27 additions and 21 deletions
+11 -6
View File
@@ -112,12 +112,6 @@ function findCodexExecutable() {
const binaryPath = findCodexExecutable();
// Use an asynchronous spawn instead of spawnSync so that Node is able to
// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
// executing. This allows us to forward those signals to the child process
// and guarantees that when either the child terminates or the parent
// receives a fatal signal, both processes exit in a predictable manner.
function isPnpmOwnedCodexInstall(nodeModulesDir) {
if (!existsSync(path.join(nodeModulesDir, ".modules.yaml"))) {
return false;
@@ -239,6 +233,17 @@ delete env.CODEX_MANAGED_BY_PNPM;
delete env.CODEX_MANAGED_BY_VITE_PLUS;
env[packageManagerEnvVar] = "1";
// Replace Node so the native binary receives signals and exits directly.
// Node exposes execve on Windows too, but calling it there is unsupported.
if (process.platform !== "win32" && typeof process.execve === "function") {
// Node ignores SIGXFSZ, and ignored signals survive execve. Installing a
// handler lets execve restore the default disposition in the native binary.
process.on("SIGXFSZ", () => {});
process.execve(binaryPath, [binaryPath, ...process.argv.slice(2)], env);
}
// Older Node versions and Windows need a child process. Spawn asynchronously
// so Node can forward signals while the native binary is executing.
const child = spawn(binaryPath, process.argv.slice(2), {
stdio: "inherit",
env,