openInEditor: spawn the editor without spawnSync's signal forwarding - #31297
openInEditor: spawn the editor without spawnSync's signal forwarding#31297robobun wants to merge 3 commits into
Conversation
|
Updated 2:06 AM PT - Aug 16th, 2026
✅ @robobun, your commit 1e7db03967b64508bf3b703a5222bc8ac5951d71 passed in 🧪 To try this PR locally: bunx bun-pr 31297That installs a local version of the PR into your bun-31297 --bun |
|
Found 1 issue this PR may fix:
🤖 Generated with Claude Code |
|
Warning Review limit reached
Next review available in: 14 minutes Limit details: You’ve used all 5 included reviews currently available under your plan. Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
WalkthroughThe PR refactors ChangesopenInEditor Process Spawning Refactor
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/js/bun/util/open-in-editor-gc.test.ts`:
- Around line 76-79: The test is using Buffer inside a try/catch which hides
failures from the initial coercion step in open_in_editor; change the call in
the loop to pass a guaranteed-valid string path (e.g., "/dev/null" or
"C:\\Windows\\System32\\drivers\\etc\\hosts" depending on platform) to
open_in_editor/openInEditor so the pre-spawn coercion runs and any unexpected
throws surface, and remove the empty catch so that failures that occur before
Editor::open() are not swallowed; update the loop that calls
Bun.openInEditor(Buffer) to call Bun.openInEditor(validPathString) and allow
exceptions to propagate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 257e542e-e569-4089-a22b-d030b0995d1c
📒 Files selected for processing (2)
src/runtime/cli/open.rstest/js/bun/util/open-in-editor-gc.test.ts
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
|
On the possible-duplicate note: #31195 (open) targets the same underlying problem reported in #31194, via a different approach — it adds a This PR instead takes the detached editor helper off the |
39c2537 to
6d35553
Compare
There was a problem hiding this comment.
Thanks for addressing the comment cleanup — no further issues from my side. Not auto-approving since this and #31195 take different approaches to the same fix and the author flagged that as a maintainer call; the spawn/signal-handling path change is also worth a human sign-off.
Extended reasoning...
Overview
This PR changes auto_close in src/runtime/cli/open.rs — the function that runs on a detached std::thread per Bun.openInEditor() call — to use bun_core::spawn_sync_inherit (a minimal posix_spawn + waitpid / std::process::Command helper) instead of sync::spawn (the full bun.spawnSync machinery). The net diff is ~28 lines removed and ~6 added in the Rust source, plus a new Linux-only stress test in test/js/bun/util/open-in-editor-gc.test.ts and a few comment updates. The change restores parity with the original Zig std.process.Child.spawn() + wait() and, as a side effect, removes a per-call MiniEventLoop leak on Windows that was documented in the deleted FIXME.
Security risks
None identified. The change removes process-global signal-handler manipulation from a detached thread rather than adding any. The argv passed to the new helper is constructed exactly as before (reconstructed from SpawnedEditorContext), and the editor binary path still comes from the same detection logic; no new external input is introduced. The xdg-open opener is now resolved via PATH (matching the Zig original) rather than as a cwd-relative path, which is if anything a minor improvement.
Level of scrutiny
Medium. The diff is small and is a simplification, but it sits at the intersection of process spawning, detached threads, and process-wide signal state — an area where this repo has already seen three related fuzzer fingerprints (#31183, #30956, this one). More importantly, there is an open competing PR (#31195) that fixes the same issue by adding a forward_signals option to sync::spawn and keeping the editor helper on the spawnSync path. The author explicitly noted in the thread that the choice between the two approaches is a maintainer decision. That is a design call I should not make on their behalf.
Other factors
All prior review feedback has been addressed: the CodeRabbit suggestion to pass a plain string path in the new test was applied (commit 318f5cb / "/dev/null"), and my earlier nit about three stale comments pointing back at bun.spawnSync was cleaned up (commit 6d35553). The bug-hunting system found no issues. The new test is acknowledged by the author as exercising the crash scenario rather than failing deterministically pre-fix (the underlying race is timing-dependent), which is consistent with the existing test in the same file but means CI coverage is best-effort. I verified bun_core::spawn_sync_inherit exists and is a thin posix_spawn + waitpid wrapper with no signal-handler side effects. Given the competing-approach decision and the signal-handling subject matter, deferring to a human reviewer.
|
CI triage for build 60248: the only failure was |
|
Status update: the retriggered build (#60262) is failing again while still in progress. The previous build's only failure was I've used my one CI retrigger, so I'll leave this as-is rather than pushing more empty commits. The branch is rebased on current main, conflict-free, and all review feedback is addressed — once the bunx upstream breakage clears (or the test is fixed on main), a fresh build should go green. |
4ed3d07 to
e07dfde
Compare
e07dfde to
901d01f
Compare
| // Not `process::sync::spawn`: that is the foreground spawnSync used by `bun run`, | ||
| // and it installs process-wide signal-forwarding handlers | ||
| // (`Bun__registerSignalsForForwarding`) that are only valid on the main thread. | ||
| // This runs on a detached thread per editor open while the user's JS keeps | ||
| // running, so it must not touch process signal state. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| // Not `process::sync::spawn`: it installs `bun run`'s process-wide signal | ||
| // forwarding, and the user's JS is still running while this thread waits. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
|
Removed the comment above the editor spawn in f155233 rather than trimming it again. The reason for not using |
There was a problem hiding this comment.
LGTM — one-line swap to the established bun_core::spawn_sync_inherit helper, restoring the original Zig std.process.Child behavior.
What was reviewed:
- Confirmed
spawn_sync_inherit(util.rs:4654) does PATH lookup for bare argv[0] on Linux viawhich, usesposix_spawnpon macOS, and installs no signal handlers — matches all three PR claims. - The helper is already exercised on all platforms by
init_command,publish_command,crash_handler, andcli/mod.rs; the removed WindowsMiniEventLoopsetup is no longer needed. - Three new tests await observable conditions (pid file, marker file) rather than sleeping, drain both pipes, and the macOS-reachable SIGUSR2 test uses the
Editor::Otherargv shape ([binary, file]) which works there. - All prior feedback addressed: stale TODOs removed, test-header comment reworded, and the paragraph comment above the spawn dropped per comment-cop.
Extended reasoning...
Overview
The PR replaces the sync::spawn call in auto_close() (src/runtime/cli/open.rs) with bun_core::spawn_sync_inherit. This is a net -26 line change on the Rust side: the use crate::api::bun::process::sync import, the FIXME(windows-leak) block, the owned_argv boxing, and the sync::Options struct with its Windows MiniEventLoop::init_global setup all collapse to a single line. The test file gains three new tests (SigCgt bitmask unchanged while an editor is up, process.on("SIGUSR2") still fires, and xdg-open is resolved on PATH) plus a rewording of the existing GC-test header comment.
Security risks
None. This is a spawn-mechanism swap for a helper thread that launches a user-specified editor binary. No new inputs are accepted, no privilege boundaries are crossed, and the replacement helper is strictly less capable (no signal-handler installation, no event-loop allocation) than what it replaces.
Level of scrutiny
Low-to-medium. The Rust change is mechanical: it swaps to a helper that src/CLAUDE.md documents as the canonical "simple inherit-stdio CLI helper" and that four other call sites (init_command, publish_command, crash_handler, cli/mod.rs) already exercise across all platforms. I read spawn_sync_inherit_impl in src/bun_core/util.rs and confirmed the PR's three specific claims: it resolves a bare argv[0] via which() on Linux/FreeBSD (util.rs:4690-4700, so the xdg-open opener now works), it uses posix_spawnp on macOS (util.rs:4773), and it never touches Bun__registerSignalsForForwarding or any process-global state. The signature &[impl AsRef<[u8]>] accepts the &[&[u8]] slice the call site passes.
Other factors
The tests are well-constructed per REVIEW.md guidance: they await the actual observable condition (pid file written, marker file written) via bounded polling rather than fixed sleeps; they drain stdout/stderr/exited concurrently; they assert stderr/stdout before exitCode; the SIGUSR2 test explicitly races the two possible outcomes so a regression fails immediately with "signal was forwarded to the editor" rather than timing out. The macOS-reachable test (skipIf(isWindows)) uses an absolute-path fake-editor.sh whose basename maps to Editor::Other, so the argv shape is [binary, file] on both Linux and macOS — no OPENER prefix, so it does not depend on open/xdg-open being present.
All prior review feedback has been addressed across the PR's iterations: my earlier nit about three stale TODO comments (fixed in 39c2537), CodeRabbit's note about the Buffer argument in a since-dropped test, and two comment-cop flags about the paragraph-length comment above the spawn (removed in f155233 — the invariant is now enforced by the SigCgt and signal-handler tests instead of prose). The PR description documents that all five tests in the file pass with the fix and the three new ones fail on unmodified main.
|
Heads up: #38032 removes the |
f155233 to
0eafef1
Compare
| /// `<spawn.h>` extension the `libc` crate does not bind: keeps `fd` open in | ||
| /// the child when `POSIX_SPAWN_CLOEXEC_DEFAULT` closes everything else. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| /// src/jsc/bindings/spawn.cpp: default dispositions for every signal and an | ||
| /// empty signal mask, the same reset `bun_spawn_sys` applies. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| /// Owns an initialized `posix_spawnattr_t` + `posix_spawn_file_actions_t` | ||
| /// pair and destroys both on every exit path of the macOS spawn arm. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| // `posix_spawnp`. On Darwin the attr/file-actions objects handed to | ||
| // `posix_spawnp` and its `add*`/`setflags` helpers stay initialized inside | ||
| // `DarwinSpawnSetup` until after the spawn call. `waitpid` is passed a | ||
| // valid `&mut c_int` out-param. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
| // Same child setup as the Linux arm gets from posix_spawn_bun: every | ||
| // signal back to its default disposition (Bun ignores SIGPIPE | ||
| // process-wide), and only the stdio named in the file actions | ||
| // survives the exec. Without POSIX_SPAWN_CLOEXEC_DEFAULT the child | ||
| // would inherit every descriptor Bun opened without CLOEXEC. |
There was a problem hiding this comment.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code
There was a problem hiding this comment.
I reviewed this and didn't find any bugs. The open.rs change is a clean simplification, but the newer spawn_sync_inherit_impl macOS rework in util.rs (CLOEXEC_DEFAULT + signal reset via the new DarwinSpawnSetup) is a behavior change to a shared helper that also backs bun init, bun publish, the URL opener, and the crash handler — worth a human look to confirm that scope is intended. There are also five open comment-cop flags on the new util.rs comments and the noted interaction with #38032's xdg-open removal.
What was reviewed:
auto_closenow callsspawn_sync_inheritwith the reconstructed argv slice; no process-global state touched, PATH lookup restored, Windows MiniEventLoop path removed.- macOS
posix_spawnparm:DarwinSpawnSetupRAII pairs init/destroy on all exit paths including the mid-init failure;addinherit_npfor fds 0/1/2 underPOSIX_SPAWN_CLOEXEC_DEFAULT;posix_spawnattr_reset_signalsmatches the existing helper inspawn.cppand the flags set (SETSIGDEF|SETSIGMASK). - New tests poll observable conditions (pid file, marker files,
/proc/self/statusSigCgt) rather than sleeping; each drains stdout/stderr/exited concurrently and asserts exit code last.
Extended reasoning...
Overview
Three files. src/runtime/cli/open.rs replaces the 27-line sync::spawn block (with its Windows MiniEventLoop FIXME) in auto_close with a single bun_core::spawn_sync_inherit(&argv[..]) call and drops the process::sync import — this is the core fix for #31194. test/js/bun/util/open-in-editor-gc.test.ts reworks the existing GC test to assert every editor actually ran (marker files) and adds four new tests: SigCgt-unchanged (Linux), SIGUSR2-handler-still-runs (POSIX), xdg-open PATH lookup (Linux), and fd-not-inherited (POSIX). src/bun_core/util.rs reworks the macOS arm of spawn_sync_inherit_impl: adds a DarwinSpawnSetup RAII wrapper, binds posix_spawn_file_actions_addinherit_np and posix_spawnattr_reset_signals, and switches from a bare posix_spawnp (only file_actions when stdin=Ignore) to one that always sets POSIX_SPAWN_CLOEXEC_DEFAULT | SETSIGDEF | SETSIGMASK and explicitly inherits fds 0/1/2.
Security risks
None identified. The change removes a path where a background thread mutated process-wide signal handlers, and hardens the macOS child (no leaked fds, default signal dispositions) to match what the Linux posix_spawn_bun arm already does. No new attack surface.
Level of scrutiny
Medium-high. The open.rs change alone would be low-risk (net deletion, restores Zig-era behavior). The util.rs change raises it: spawn_sync_inherit is documented in src/CLAUDE.md as the canonical CLI spawn helper and is called from init_command.rs (×2), publish_command.rs, cli/mod.rs, crash_handler/lib.rs, and now open.rs. On macOS all of those now get fd scrubbing and signal reset where they previously inherited everything. That is almost certainly correct (it brings macOS to parity with Linux), but it's a cross-cutting behavior change to a shared primitive that the PR title/description frames as an openInEditor fix, so a maintainer should confirm the scope is intentional.
Other factors
- Five unresolved comment-cop inline comments on
util.rs(posted 2026-08-13, after the last author response) flag the new multi-line comments; the author previously addressed identical flags onopen.rsby deleting the comment, so these look like they're still pending. - The author's own note about #38032 removing the Linux
xdg-openprefix means the "finds the xdg-open opener on PATH" test here becomes a hang if that PR lands first — merge order needs coordination. - The tests are well-constructed (poll for observable conditions, wire the failing outcome to a distinct message, drain pipes concurrently, exit code asserted last,
test.concurrentfor independent subprocess tests). TheDarwinSpawnSetupRAII correctly destroysattron theactionsinit-failure path and both on drop. - My prior review comment (stale TODOs) was addressed; the current
util.rsrework and the fd/signal tests are new since then.
|
Trimmed the flagged comments in On the scope point from the review: the |
There was a problem hiding this comment.
I reviewed this PR and didn't find any bugs. Because it changes the macOS spawn_sync_inherit path (adding POSIX_SPAWN_CLOEXEC_DEFAULT + signal reset) which is shared by bun init, bun publish, the crash-handler symbolizer spawn, and the URL opener — and the author flagged a landing-order dependency with #38032 for the xdg-open test — a human look would still be worthwhile.
What was reviewed:
auto_closeswap tospawn_sync_inherit— argv slice lifetimes (backed by the boxedSpawnedEditorContextthat outlives the call), Windows path (goes throughstd::process::Command, no MiniEventLoop needed).- macOS
DarwinSpawnSetup: RAII pairing ofposix_spawnattr_init/file_actions_initwith_destroyon all paths, including the early-return whenfile_actions_initfails afterattris up. - Checked that
posix_spawnattr_reset_signals(spawn.cpp) fillssigdefaultand emptiessigmask, matching the flags set;addinherit_npfor 0-2 combined withCLOEXEC_DEFAULTis the standard Darwin inherit-only-stdio pattern. - New tests: no unbounded sleeps (all poll on observable markers/pids), pipes drained concurrently, exit code asserted last, hermetic (fake editors in tempDir, no network).
Extended reasoning...
Overview
Three files. src/runtime/cli/open.rs replaces a ~27-line sync::spawn block (with a FIXME'd per-call MiniEventLoop leak on Windows) in the detached editor thread with one call to bun_core::spawn_sync_inherit, so Bun.openInEditor no longer installs bun run's process-wide signal-forwarding handlers from a background thread. src/bun_core/util.rs reworks the macOS arm of spawn_sync_inherit_impl to set POSIX_SPAWN_CLOEXEC_DEFAULT | SETSIGDEF | SETSIGMASK, reset signals via the existing posix_spawnattr_reset_signals helper in spawn.cpp, and explicitly inherit fds 0-2 via addinherit_np — wrapped in a new DarwinSpawnSetup RAII struct so both the attr and file-actions objects are destroyed on every exit path. The test file replaces one timed test and adds four new ones covering SigCgt stability, process.on handler delivery, xdg-open PATH lookup, and fd inheritance.
Security risks
None identified. The change narrows what the spawned editor inherits (fewer fds, defaulted signals) rather than broadening it. No user-controlled input reaches new parsing.
Level of scrutiny
Moderate-to-high. The open.rs change itself is a clear simplification back toward the original Zig behavior. The util.rs change is where the review weight is: it alters macOS spawn semantics for every spawn_sync_inherit caller — crash_handler (llvm-symbolizer), bun init (self-spawn for install), bun publish (opening the auth URL), and cli/mod.rs (URL opener). Bringing macOS in line with what posix_spawn_bun already does on Linux is defensible, but it's a behavior change on a shared helper across callers that aren't exercised by this PR's tests, and it's macOS-only so it can't be verified on the Linux dev machine. The RAII wiring looks correct (init failures clean up the partially-initialized state; Drop handles the rest), and the fd test is designed to catch regressions on the macOS lanes.
Other factors
- The author explicitly noted that #38032 removes the
xdg-openprefix from the vim/nvim/emacs argv on Linux, which would make the new "finds the xdg-open opener on PATH" test wait forever. Whichever lands second needs to adjust that test — a maintainer should sequence these. - This is one of three overlapping fixes for #31194 (also #31195, #31299) with different approaches; a maintainer already needs to pick one.
- My prior inline nit (stale TODO comments pointing back at spawnSync) was addressed. The subsequent comment-cop flags on
util.rsappear addressed by 8a14996 (comments in the current diff are one-liners plus required SAFETY blocks). - CI on 0eafef1 showed one unrelated failure (
test-cluster-shared-leak.jstimeout on Windows x64); the latest commit only trims comments.
Bun.openInEditor waits for the editor on a detached thread. That thread went through process::sync::spawn, the foreground spawnSync used by bun run, which installs process-wide signal-forwarding handlers for the lifetime of the child. With the user's program still running on the main thread, signals such as SIGUSR2 or SIGINT were redirected to the editor instead of reaching the process's own handlers, and overlapping editor opens raced on the shared previous_actions table. Use bun_core::spawn_sync_inherit, a plain spawn and wait with inherited stdio, which also resolves the opener on PATH and removes the per-open MiniEventLoop allocation the sync::spawn path needed on Windows. Co-authored-by: Minh Vu <38443830+fallintoplace@users.noreply.github.com>
…ild too The Linux arm goes through posix_spawn_bun, which resets every signal to its default and closes everything above the stdio it sets up. The macOS arm was a bare posix_spawnp with no attributes, so the child inherited every descriptor Bun had opened without CLOEXEC and Bun's ignored signals. Set POSIX_SPAWN_CLOEXEC_DEFAULT plus the signal reset and inherit only fds 0-2 (or /dev/null on 0), matching what bun_spawn_sys does for its spawns. The editor tests now verify this with bun itself as the editor, replace the timed GC test with one that waits for every editor to have run, and no longer describe the spawnSync SIGPWR history the editor path no longer touches.
8a14996 to
1e7db03
Compare
There was a problem hiding this comment.
I reviewed this PR and didn't find any bugs. Because it changes the shared spawn_sync_inherit macOS path (fd scrubbing + signal reset) for every caller — bun init, bun publish, the URL opener, the crash-handler symbolizer — and the author has flagged both a competing approach (#31195) and a known test interaction with #38032 for a maintainer to decide, a human look would still be worthwhile.
What was reviewed:
auto_closenow usesspawn_sync_inherit(no signal forwarding, no MiniEventLoop leak) — matches the original Zigstd.process.Childshape.DarwinSpawnSetupRAII: attr/actions destroyed on every exit path;posix_spawnattr_reset_signalsextern matchesspawn.cpp;addinherit_npsignature matches the one already inspawn_sys/posix_spawn.rs.- New tests poll observable conditions (pid file, marker files,
SigCgt) rather than sleeping, and gate per-platform correctly.
Extended reasoning...
Overview
Three files: src/runtime/cli/open.rs swaps the detached editor thread's spawn from process::sync::spawn (the bun run foreground path with process-wide signal forwarding) to bun_core::spawn_sync_inherit, deleting the Windows MiniEventLoop FIXME block. src/bun_core/util.rs hardens the macOS arm of spawn_sync_inherit_impl to set POSIX_SPAWN_CLOEXEC_DEFAULT + SETSIGDEF/SETSIGMASK, explicitly inherit only fds 0–2, and wraps the attr/actions pair in a DarwinSpawnSetup struct with a Drop impl. test/js/bun/util/open-in-editor-gc.test.ts rewrites one test and adds four new ones covering signal dispositions, process.on handler delivery, xdg-open PATH lookup, and fd inheritance.
Security risks
None identified. The change tightens child-process isolation on macOS (fewer inherited fds, reset signal mask/dispositions). No user-controlled input reaches new parsing.
Level of scrutiny
Medium-high. The open.rs change itself is small and clearly right, but the util.rs change is a behavioral change to a shared helper on macOS that reaches four other call sites (init_command.rs, publish_command.rs, cli/mod.rs URL opener, crash_handler/lib.rs symbolizer). The author only compile-checked it on darwin targets and deferred runtime verification to CI's macOS lanes. Spawn attribute setup on Darwin (addinherit_np, CLOEXEC_DEFAULT, the c_short flags cast) is subtle enough that a maintainer familiar with the macOS spawn path should confirm it matches spawn_sys's established pattern.
Other factors
- The author explicitly left it to maintainers to choose between this PR and #31195 (which instead adds a
forward_signalsopt-out tosync::spawn). That is a design decision a human should make. - The author flagged that #38032 removes the non-macOS
xdg-openprefix, which will break the new "finds the xdg-open opener on PATH" test if #38032 lands first — a merge-order coordination point. - My earlier nit (stale TODO comments pointing back at
bun.spawnSync) was addressed in 39c2537; the comment-cop bot's verbose-comment flags were addressed in f155233 and 8a14996 / 1e7db03. - Test quality is good: conditions are awaited via marker/pid files rather than sleeps, subprocess pipes are drained concurrently, exit codes asserted last, platform gating uses
isLinux/isWindows, and each test has a clear failure mode on the unfixed build per the PR description.
Problem
Bun.openInEditor()installsspawnSync's signal forwarding for as long as the editor runs (Bun.openInEditor can install spawnSync signal forwarding from editor helper thread #31194). While an editor is open, SIGINT/SIGTERM/SIGHUP/SIGUSR2/... sent to the process are redirected to the editor instead of reaching the program's own handlers: aprocess.on("SIGUSR2")listener never runs, the editor gets the signal. Two overlapping opens additionally race on the sharedprevious_actions[]table: the second registration saves the forwarding handler as the "previous" disposition, so after both editors exit the forwarded signals are left at SIG_DFL, wiping the program's handlers.auto_close()insrc/runtime/cli/open.rs, which runs on a detached thread per open, calledprocess::sync::spawn(). That is the foregroundspawnSyncbehindbun run;spawn_posix()(src/spawn/process.rs) unconditionally registers the forwarding handlers (Bun__registerSignalsForForwarding,c-bindings.cpp, whose comment says it is only ever used on the main thread) and restores them when the child exits. The Zig implementation used a plainstd.process.Childspawn here; the Rust port switched it tosync::spawn.xdg-openopener used for vim/emacs/neovim on Linux relative to cwd (the sync spawn path does no PATH lookup), so that spawn silently failed, and on Windows it allocated aMiniEventLoopper open that was never torn down.Fix
auto_close()usesbun_core::spawn_sync_inherit(): spawn with inherited stdio, wait, nothing else. It touches no process-wide state, resolves a bare argv[0] on PATH, and needs no event loop on Windows, so the FIXME block goes away with thesync::spawncall.bun runchild sees the terminal's signals. An editor launched from inside a running program is not that case, so the editor thread should not be on that code path at all, rather than that path growing an opt-out for one caller (the approach in fix(openInEditor): disable spawnSync signal forwarding for editor helper #31195).spawn_sync_inherit()'s macOS arm (src/bun_core/util.rs) was a bareposix_spawnpwith no attributes, so unlike its Linux arm (which goes throughposix_spawn_bunand scrubs the child) it handed the editor every descriptor Bun had opened without CLOEXEC (fs.openSyncfds, for example) and Bun's ignored signals. Switching the editor onto the helper would have regressed that on macOS relative to thesync::spawnpath, so the helper now setsPOSIX_SPAWN_CLOEXEC_DEFAULTand the same signal resetbun_spawn_sysuses, inheriting only fds 0-2 (or/dev/nullon 0). This also applies to the helper's existing callers (bun init,bun publish, the URL opener). Cross-checked withcargo check -p bun_core --target aarch64-apple-darwin; the runtime behavior is covered by the fd test below on the macOS lanes.bun bd test test/js/bun/util/open-in-editor-gc.test.ts(6 tests, repeated runs clean). The three forwarding tests keep a fake editor alive while asserting; on unmodified main all three fail (SigCgtgains the 14 forwarded signals[1,2,3,5,6,12,14,15,16,24,25,26,29,31], the SIGUSR2 listener is bypassed and the editor dies from the forwarded signal, and the opener is never spawned); with the fix everything passes.Bun.openInEditor does not change the process's signal dispositions(Linux):SigCgtfrom/proc/self/statusis identical before and while the editor is up.a process.on signal handler still runs while an editor ... is up(POSIX): the listener runs; the alternative outcome (editor killed by the forwarded signal) is detected explicitly so the failure is immediate rather than a timeout.Bun.openInEditor finds the xdg-open opener on PATH(Linux): a fakexdg-openon PATH records that it was invoked.Bun.openInEditor does not pass the process's file descriptors to the editor(POSIX): bun itself is used as the editor and checks whether the parent'sfs.openSyncdescriptor number still refers to the sentinel file. Passes on Linux with or without this PR (the Linux spawner always scrubbed); on macOS it fails without theutil.rschange and passes with it.alternating editors on live editor threads, then GC(POSIX) replaces the old timed "does not break GC signal handling" test. That test was written for Don't forward SIGPWR in spawnSync signal handling #31183's SIGPWR exclusion in the forwarding list, which the editor path no longer reaches, so it passed both with and without this change and its 1 s sleep no longer had a reason; the replacement keeps what it still covered (the editor name being replaced while earlier threads are live, then a forced GC) and waits for every editor to have run instead of sleeping. The SIGPWR exclusion itself is unchanged and still applies to the remainingsync::spawncallers; it just has no test driving it fromopenInEditorany more.Background
spawnSyncsignal forwarding: whenbun run/bunxrun a script as a child, Bun installs handlers for npm's signal list thatkill()the current child (Bun__currentSyncPID) and restores the previous dispositions afterwards. The saved dispositions live in one global array, so it is only correct for one foreground child at a time on the main thread.Bun.openInEditor()builds the editor argv and hands it to a detached thread (auto_close) that spawns the editor and waits for it to exit, while JS continues on the main thread.bun_core::spawn_sync_inherit()is the minimal spawn+wait helper already used bybun init,bun publishand the URL opener incli/mod.rs(posix_spawnwith PATH lookup on POSIX,CreateProcessWviastd::process::Commandon Windows).SigCgtin/proc/<pid>/statusis the bitmask of signals the process currently has a handler installed for, which makes "did anything touch the dispositions" directly observable.Related
forward_signalsoption to the sync spawn options instead; its signal-listener scenario is folded in here, credited in the commit).Fixes #31194
no test proof · iteration 10 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/util/open-in-editor-gc.test.ts