GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — process: throw on execve(2) failure instead of aborting by bengl · Pull Request #62878 · nodejs/node

Conversation

@bengl

@bengl bengl commented Apr 21, 2026

Copy link
Copy Markdown
Member

When the underlying execve(2) system call fails, process.execve() previously printed an error to stderr and called ABORT(), preventing JS code from detecting or recovering from common failures such as a missing binary. Throw an ErrnoException instead, carrying the usual code, errno, syscall, and path properties.

To leave the process in a clean state when execve(2) fails, no longer run native AtExit callbacks before the call (their in-memory effects are discarded on success anyway), and snapshot and restore the FD_CLOEXEC flags on stdio so a failed call has no observable side effects. Rename and update test-process-execve-abort.js accordingly and document the new behaviour.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Apr 21, 2026
@codecov

codecov Bot commented Apr 21, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 52.63158% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.62%. Comparing base (c29a34c) to head (55e4b8f).
⚠️ Report is 91 commits behind head on main.

Files with missing lines Patch % Lines
src/node_process_methods.cc 52.63% 7 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #62878      +/-   ##
==========================================
- Coverage   89.62%   89.62%   -0.01%     
==========================================
  Files         706      706              
  Lines      219165   219170       +5     
  Branches    41989    42000      +11     
==========================================
+ Hits       196435   196439       +4     
+ Misses      14621    14615       -6     
- Partials     8109     8116       +7     
Files with missing lines Coverage Δ
src/node_process_methods.cc 88.52% <52.63%> (+0.34%) ⬆️

... and 30 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bengl
process: throw on execve(2) failure instead of aborting
55e4b8f
When the underlying execve(2) system call fails, process.execve()
previously printed an error to stderr and called ABORT(), preventing
JS code from detecting or recovering from common failures such as a
missing binary. Throw an ErrnoException instead, carrying the standard
code, errno, syscall, and path properties.

To leave the process in a clean state when execve(2) fails, no longer
run native AtExit callbacks before the call (their in-memory effects
are discarded on success anyway), and snapshot and restore the
FD_CLOEXEC flags on stdio so a failed call has no observable side
effects. Rename and update test-process-execve-abort.js accordingly
and document the new behavior.

Signed-off-by: Bryan English <[email protected]>
@bengl
bengl force-pushed the bengl/execve-throws branch from 55e9403 to 55e4b8f Compare April 22, 2026 00:17
@bengl bengl added the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Apr 22, 2026
@bengl
bengl requested a review from ShogunPanda April 22, 2026 00:39
ShogunPanda
ShogunPanda approved these changes Apr 22, 2026
View reviewed changes
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Apr 22, 2026
@nodejs-github-bot

nodejs-github-bot commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/72851/

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nodejs-github-bot

nodejs-github-bot commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/72852/

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nodejs-github-bot

nodejs-github-bot commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/72853/

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bengl
bengl requested a review from jasnell April 22, 2026 13:30
@bengl

bengl commented Apr 22, 2026

Copy link
Copy Markdown
Member Author

@jasnell adding you as a reviewer since you had the initial suggestion to just abort when execve fails. The approach I took here is a bit of a departure, since RunAtExit is no longer called. I think this approach makes sense because the process isn't so much "exiting" as being replaced entirely, as is the nature of execve. The benefit of being able to recover from ENOENT, for example, seems worth it.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ShogunPanda

ShogunPanda commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

@bengl Do you want to wait for @jasnell or shall we merge this?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

mcollina
mcollina approved these changes Apr 27, 2026
View reviewed changes

@mcollina mcollina left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mcollina mcollina added semver-minor PRs that contain new features and should be released in the next minor version. baking-for-lts PRs whose changes need more time before an LTS backport. labels Apr 27, 2026
@ShogunPanda ShogunPanda added commit-queue PRs queued for automated landing through the Commit Queue. commit-queue-squash PRs the Commit Queue should land as one squashed commit. labels Apr 27, 2026
@nodejs-github-bot nodejs-github-bot removed the commit-queue PRs queued for automated landing through the Commit Queue. label Apr 27, 2026
@nodejs-github-bot
nodejs-github-bot merged commit 0a9b67e into nodejs:main Apr 27, 2026
79 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@nodejs-github-bot

nodejs-github-bot commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Landed in 0a9b67e

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

aduh95 pushed a commit that referenced this pull request May 5, 2026
@bengl @aduh95
process: throw on execve(2) failure instead of aborting
ab66de8
When the underlying execve(2) system call fails, process.execve()
previously printed an error to stderr and called ABORT(), preventing
JS code from detecting or recovering from common failures such as a
missing binary. Throw an ErrnoException instead, carrying the standard
code, errno, syscall, and path properties.

To leave the process in a clean state when execve(2) fails, no longer
run native AtExit callbacks before the call (their in-memory effects
are discarded on success anyway), and snapshot and restore the
FD_CLOEXEC flags on stdio so a failed call has no observable side
effects. Rename and update test-process-execve-abort.js accordingly
and document the new behavior.

Signed-off-by: Bryan English <[email protected]>
PR-URL: #62878
Reviewed-By: Paolo Insogna <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
@aduh95 aduh95 mentioned this pull request May 5, 2026
2026-05-07, Version 26.1.0 #63137
Merged
aduh95 added a commit that referenced this pull request May 5, 2026
@aduh95
2026-05-07, Version 26.1.0 (Current)
0326a80
Notable changes:

buffer:
  * (SEMVER-MINOR) add `end` parameter (Robert Nagy) #62390
crypto:
  * (SEMVER-MINOR) accept key data in `crypto.diffieHellman()` and cleanup DH jobs (Filip Skokan) #62527
  * (SEMVER-MINOR) implement `randomUUIDv7()` (nabeel378) #62553
debugger:
  * (SEMVER-MINOR) add edit-free runtime expression probes to `node inspect` (Joyee Cheung) #62713
fs:
  * (SEMVER-MINOR) add `signal` option to `fs.stat()` (Mert Can Altin) #57775
  * (SEMVER-MINOR) expose frsize field in statfs (Jinho Jang) #62277
http:
  * (SEMVER-MINOR) harden `ClientRequest` options merge (Matteo Collina) #63082
  * (SEMVER-MINOR) add `req.signal` to `IncomingMessage` (Akshat) #62541
lib,src,test,doc:
  * (SEMVER-MINOR) add `node:ffi` module (Colin Ihrig) #62072
process:
  * (SEMVER-MINOR) throw on `execve(2)` failure instead of aborting (Bryan English) #62878
src:
  * (SEMVER-MINOR) allow empty `--experimental-config-file` (Marco Ippolito) #61610
stream:
  * (SEMVER-MINOR) propagate destruction in `duplexPair` (Ahmed Elhor) #61098
test_runner:
  * (SEMVER-MINOR) align mock timeout api (sangwook) #62820
  * (SEMVER-MINOR) add mock-timers support for `AbortSignal.timeout` (DeveloperViraj) #60751
  * (SEMVER-MINOR) support test order randomization (Pietro Marchini) #61747
util:
  * (SEMVER-MINOR) colorize text with hex colors (Guilherme Araújo) #61556

PR-URL: #63137
aduh95 added a commit that referenced this pull request May 6, 2026
@aduh95
2026-05-07, Version 26.1.0 (Current)
e7da6f0
Notable changes:

buffer:
  * (SEMVER-MINOR) add `end` parameter (Robert Nagy) #62390
crypto:
  * (SEMVER-MINOR) accept key data in `crypto.diffieHellman()` and cleanup DH jobs (Filip Skokan) #62527
  * (SEMVER-MINOR) implement `randomUUIDv7()` (nabeel378) #62553
debugger:
  * (SEMVER-MINOR) add edit-free runtime expression probes to `node inspect` (Joyee Cheung) #62713
fs:
  * (SEMVER-MINOR) add `signal` option to `fs.stat()` (Mert Can Altin) #57775
  * (SEMVER-MINOR) expose frsize field in statfs (Jinho Jang) #62277
http:
  * (SEMVER-MINOR) harden `ClientRequest` options merge (Matteo Collina) #63082
  * (SEMVER-MINOR) add `req.signal` to `IncomingMessage` (Akshat) #62541
lib,src,test,doc:
  * (SEMVER-MINOR) add `node:ffi` module (Colin Ihrig) #62072
process:
  * (SEMVER-MINOR) throw on `execve(2)` failure instead of aborting (Bryan English) #62878
src:
  * (SEMVER-MINOR) allow empty `--experimental-config-file` (Marco Ippolito) #61610
stream:
  * (SEMVER-MINOR) propagate destruction in `duplexPair` (Ahmed Elhor) #61098
test_runner:
  * (SEMVER-MINOR) align mock timeout api (sangwook) #62820
  * (SEMVER-MINOR) add mock-timers support for `AbortSignal.timeout` (DeveloperViraj) #60751
  * (SEMVER-MINOR) support test order randomization (Pietro Marchini) #61747
util:
  * (SEMVER-MINOR) colorize text with hex colors (Guilherme Araújo) #61556

PR-URL: #63137
aduh95 added a commit that referenced this pull request May 7, 2026
@aduh95
2026-05-07, Version 26.1.0 (Current)
a725fe1
Notable changes:

buffer:
  * (SEMVER-MINOR) add `end` parameter (Robert Nagy) #62390
crypto:
  * (SEMVER-MINOR) accept key data in `crypto.diffieHellman()` and cleanup DH jobs (Filip Skokan) #62527
  * (SEMVER-MINOR) implement `randomUUIDv7()` (nabeel378) #62553
debugger:
  * (SEMVER-MINOR) add edit-free runtime expression probes to `node inspect` (Joyee Cheung) #62713
fs:
  * (SEMVER-MINOR) add `signal` option to `fs.stat()` (Mert Can Altin) #57775
  * (SEMVER-MINOR) expose frsize field in statfs (Jinho Jang) #62277
http:
  * (SEMVER-MINOR) harden `ClientRequest` options merge (Matteo Collina) #63082
  * (SEMVER-MINOR) add `req.signal` to `IncomingMessage` (Akshat) #62541
lib,src,test,doc:
  * (SEMVER-MINOR) add `node:ffi` module (Colin Ihrig) #62072
process:
  * (SEMVER-MINOR) throw on `execve(2)` failure instead of aborting (Bryan English) #62878
src:
  * (SEMVER-MINOR) allow empty `--experimental-config-file` (Marco Ippolito) #61610
stream:
  * (SEMVER-MINOR) propagate destruction in `duplexPair` (Ahmed Elhor) #61098
test_runner:
  * (SEMVER-MINOR) align mock timeout api (sangwook) #62820
  * (SEMVER-MINOR) add mock-timers support for `AbortSignal.timeout` (DeveloperViraj) #60751
  * (SEMVER-MINOR) support test order randomization (Pietro Marchini) #61747
util:
  * (SEMVER-MINOR) colorize text with hex colors (Guilherme Araújo) #61556

PR-URL: #63137
@robobun robobun mentioned this pull request Jul 2, 2026
process.execve: throw on execve(2) failure instead of aborting oven-sh/bun#33246
Merged
Jarred-Sumner added a commit to oven-sh/bun that referenced this pull request Jul 4, 2026
@robobun @Jarred-Sumner
process.execve: throw on execve(2) failure instead of aborting (#33246)
800a66b
### Problem

When the underlying `execve(2)` syscall fails (missing binary,
permission denied, ...), `process.execve()` prints an error to stderr
and calls `abort()`, so JavaScript has no way to observe or recover from
the failure:

```js
try {
  process.execve("/does/not/exist", ["/does/not/exist"]);
} catch {
  // never reached; the process dies with SIGABRT
}
```

```
process.execve failed with error code ENOENT
SystemError [process.execve]: execve No such file or directory: /does/not/exist
    at execve (node:internal/process/per_thread:0:0)
Aborted
```

Node changed this in nodejs/node#62878 (shipped in v26): a failed
`execve(2)` now throws an `ErrnoException` and leaves the process in a
clean, recoverable state.

### Fix

`src/jsc/bindings/BunProcess.cpp` (`Process_functionExecve`):

- Throw Node's `ErrnoException` shape instead of printing and calling
`abort()`:
  ```
  Error: ENOENT, No such file or directory '/does/not/exist'
    code: 'ENOENT', errno: 2, syscall: 'execve', path: '/does/not/exist'
  ```
It is built at the call site rather than through `createSystemError`
because Node's error here is a plain `Error`, not a `SystemError`, and
it carries a `path`.
- `persistStandardStream` now returns the previous `F_GETFD` flags, and
a failed `execve(2)` restores the `FD_CLOEXEC` bits it cleared on fds
0-2, matching Node. A partial failure of the fcntl loop also restores
the fds already changed before throwing.
- The Linux path restores the signal mask it cleared before the call.
The Darwin `posix_spawn(POSIX_SPAWN_SETEXEC)` path has no such mutation.
- The Linux `close_range(CLOSE_RANGE_CLOEXEC)` on fds >= 3 is
Bun-specific hardening Node does not have, and it is intentionally not
undone on failure: `close_range` reports nothing per fd, and
`FD_CLOEXEC` only takes effect at the next `exec`, so the still-running
image is unaffected.

No behavior change on success or on Windows (where `process.execve` is
unavailable).

### Tests

- `test/js/node/test/parallel/test-process-execve-throws.js`: vendored
verbatim from Node (added in the same Node PR), replacing
`test-process-execve-abort.js`, which Node deleted.
- `test/js/node/process/process-execve.test.ts`:
- the old "aborts with ENOENT" test becomes "throws ENOENT", asserting
the full error shape and that the process exits cleanly afterwards
- new test: a caught failure leaves the process able to `execve` a
different, valid path, proving no state was left behind
- `test/js/bun/util/fuzzilli-reprl.test.ts`: dropped
`expect(stderr).not.toContain("process.execve failed")`; that string no
longer exists in the codebase so the assertion can never fail. The
load-bearing assertion (the REPRL loop survives the payload) is
unchanged.

On current `main` both new tests fail because the child aborts (exit
134, empty stdout); with this change all pass, including under
`BUN_JSC_validateExceptionChecks=1`.

Note: #31831 includes the same behavior change as part of a larger Node
v26.3.0 process-compat port. This is the focused standalone version;
happy to close either in favor of the other.

---------

Co-authored-by: Jarred Sumner <[email protected]>
liooil pushed a commit to liooil/poly that referenced this pull request Aug 7, 2026
@robobun @Jarred-Sumner
process.execve: throw on execve(2) failure instead of aborting (#33246)
a488fa3
### Problem

When the underlying `execve(2)` syscall fails (missing binary,
permission denied, ...), `process.execve()` prints an error to stderr
and calls `abort()`, so JavaScript has no way to observe or recover from
the failure:

```js
try {
  process.execve("/does/not/exist", ["/does/not/exist"]);
} catch {
  // never reached; the process dies with SIGABRT
}
```

```
process.execve failed with error code ENOENT
SystemError [process.execve]: execve No such file or directory: /does/not/exist
    at execve (node:internal/process/per_thread:0:0)
Aborted
```

Node changed this in nodejs/node#62878 (shipped in v26): a failed
`execve(2)` now throws an `ErrnoException` and leaves the process in a
clean, recoverable state.

### Fix

`src/jsc/bindings/BunProcess.cpp` (`Process_functionExecve`):

- Throw Node's `ErrnoException` shape instead of printing and calling
`abort()`:
  ```
  Error: ENOENT, No such file or directory '/does/not/exist'
    code: 'ENOENT', errno: 2, syscall: 'execve', path: '/does/not/exist'
  ```
It is built at the call site rather than through `createSystemError`
because Node's error here is a plain `Error`, not a `SystemError`, and
it carries a `path`.
- `persistStandardStream` now returns the previous `F_GETFD` flags, and
a failed `execve(2)` restores the `FD_CLOEXEC` bits it cleared on fds
0-2, matching Node. A partial failure of the fcntl loop also restores
the fds already changed before throwing.
- The Linux path restores the signal mask it cleared before the call.
The Darwin `posix_spawn(POSIX_SPAWN_SETEXEC)` path has no such mutation.
- The Linux `close_range(CLOSE_RANGE_CLOEXEC)` on fds >= 3 is
Bun-specific hardening Node does not have, and it is intentionally not
undone on failure: `close_range` reports nothing per fd, and
`FD_CLOEXEC` only takes effect at the next `exec`, so the still-running
image is unaffected.

No behavior change on success or on Windows (where `process.execve` is
unavailable).

### Tests

- `test/js/node/test/parallel/test-process-execve-throws.js`: vendored
verbatim from Node (added in the same Node PR), replacing
`test-process-execve-abort.js`, which Node deleted.
- `test/js/node/process/process-execve.test.ts`:
- the old "aborts with ENOENT" test becomes "throws ENOENT", asserting
the full error shape and that the process exits cleanly afterwards
- new test: a caught failure leaves the process able to `execve` a
different, valid path, proving no state was left behind
- `test/js/bun/util/fuzzilli-reprl.test.ts`: dropped
`expect(stderr).not.toContain("process.execve failed")`; that string no
longer exists in the codebase so the assertion can never fail. The
load-bearing assertion (the REPRL loop survives the payload) is
unchanged.

On current `main` both new tests fail because the child aborts (exit
134, empty stdout); with this change all pass, including under
`BUN_JSC_validateExceptionChecks=1`.

Note: #31831 includes the same behavior change as part of a larger Node
v26.3.0 process-compat port. This is the focused standalone version;
happy to close either in favor of the other.

---------

Co-authored-by: Jarred Sumner <[email protected]>
@fullofcaffeine fullofcaffeine mentioned this pull request Aug 30, 2026
fix(tooling): raw-exec admitted Haxe binaries fullofcaffeine/genes-ts#181
Merged
This was referenced Nov 9, 2026
fix(launcher): exec canon in place of the node launcher journeyWorker/canon#144
Merged
release(0.39.1): npm launcher execs canon journeyWorker/canon#145
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@mcollina mcollina mcollina approved these changes
@ShogunPanda ShogunPanda ShogunPanda approved these changes
@jasnell jasnell Awaiting requested review from jasnell

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

baking-for-lts PRs whose changes need more time before an LTS backport. c++ Issues and PRs that require attention from people who are familiar with C++. commit-queue-squash PRs the Commit Queue should land as one squashed commit. needs-ci PRs that need a full CI run. semver-minor PRs that contain new features and should be released in the next minor version.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

4 participants

@bengl @nodejs-github-bot @ShogunPanda @mcollina
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.