You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Any wt command that waits on a child process via shell_exec::Cmd::delayed_stream aborts with SIGABRT if the SIGCHLD notification write fails. wt inherits this from wait-timeout 0.2.1, whose SIGCHLD handler panics on any errno other than WouldBlock — and because the handler is extern "C", the panic cannot unwind and goes straight to abort().
I hit this reliably by running wt switch --create inside the OpenAI Codex CLI's Linux sandbox, which denies the sendto(2) syscall. But the sandbox is only the trigger; the crash-instead-of-degrade behaviour is worktrunk's to absorb.
bad error on write fd: Operation not permitted (os error 1)
Mechanism
wait-timeout builds a self-notification AF_UNIX socketpair and pokes it from the SIGCHLD handler with send(fd, buf, 1, MSG_NOSIGNAL). Its notify() tolerates exactly one error:
match state.write.write(&[1]){Ok(..) => {}Err(e)if e.kind() == WouldBlock => {}Err(e) => panic!("bad error on write fd: {}", e),// ← anything else aborts the process}
sigchld_handler is extern "C", so that panic hits panic_cannot_unwind and terminates the process immediately. There is no way for wt to catch it and no useful diagnostic for the user — just SIGABRT.
(codex sandbox runs the command under Codex's default workspace-write sandbox, which denies sendto. Confirmed independently: send() on an AF_UNIX socketpair returns EPERM there while write() on the same fd succeeds.)
I have two independent coredumps six days apart — different repositories, different branches, different terminal sessions — with byte-identical stacks, so this is not a race.
Expected
Failing to poke an internal wakeup socket should not kill the process. Ideally wt either reports a normal error or, better, doesn't have this failure mode at all.
Notes on a fix
This is a known problem in the crate: alexcrichton/wait-timeout#45 flags exactly this notify() panic as an async-signal-safety violation (panic! in a signal handler allocates and takes locks). It's open and currently classified as minor/theoretical; this report is a real-world instance of it. Options, roughly in order of preference:
Replace wait-timeout in src/shell_exec.rs. Since delayed_stream already polls, waiting on the child directly (e.g. pidfd on Linux — cf. Add pidfd-based implementation alexcrichton/wait-timeout#29 — or a waitpid-with-timeout loop) removes the global SIGCHLD handler entirely.
Vendor/patch notify() to ignore all write errors, not just WouldBlock. A missed wakeup is at worst a delayed timeout; it is never worth an abort().
src/commands/picker/pager.rs and src/commands/picker/prs.rs use wait_timeout too and presumably have the same exposure.
Environment
worktrunk 0.68.0-1 (Arch extra); wait-timeout 0.2.1 is still in Cargo.lock on v0.74.0, and src/shell_exec.rs still uses it, so I believe current main is affected
Summary
Any
wtcommand that waits on a child process viashell_exec::Cmd::delayed_streamaborts withSIGABRTif theSIGCHLDnotification write fails.wtinherits this fromwait-timeout0.2.1, whoseSIGCHLDhandler panics on any errno other thanWouldBlock— and because the handler isextern "C", the panic cannot unwind and goes straight toabort().I hit this reliably by running
wt switch --createinside the OpenAI Codex CLI's Linux sandbox, which denies thesendto(2)syscall. But the sandbox is only the trigger; the crash-instead-of-degrade behaviour is worktrunk's to absorb.What happens
Symbolized backtrace (Arch
worktrunk 0.68.0-1, symbols via debuginfod):Panic message recovered from the core dump:
Mechanism
wait-timeoutbuilds a self-notificationAF_UNIXsocketpair and pokes it from theSIGCHLDhandler withsend(fd, buf, 1, MSG_NOSIGNAL). Itsnotify()tolerates exactly one error:sigchld_handlerisextern "C", so that panic hitspanic_cannot_unwindand terminates the process immediately. There is no way forwtto catch it and no useful diagnostic for the user — justSIGABRT.Reproduction
Deterministic, in a throwaway repo:
(
codex sandboxruns the command under Codex's defaultworkspace-writesandbox, which deniessendto. Confirmed independently:send()on anAF_UNIXsocketpair returnsEPERMthere whilewrite()on the same fd succeeds.)I have two independent coredumps six days apart — different repositories, different branches, different terminal sessions — with byte-identical stacks, so this is not a race.
Expected
Failing to poke an internal wakeup socket should not kill the process. Ideally
wteither reports a normal error or, better, doesn't have this failure mode at all.Notes on a fix
This is a known problem in the crate: alexcrichton/wait-timeout#45 flags exactly this
notify()panic as an async-signal-safety violation (panic!in a signal handler allocates and takes locks). It's open and currently classified as minor/theoretical; this report is a real-world instance of it. Options, roughly in order of preference:wait-timeoutinsrc/shell_exec.rs. Sincedelayed_streamalready polls, waiting on the child directly (e.g.pidfdon Linux — cf. Add pidfd-based implementation alexcrichton/wait-timeout#29 — or awaitpid-with-timeout loop) removes the globalSIGCHLDhandler entirely.notify()to ignore all write errors, not justWouldBlock. A missed wakeup is at worst a delayed timeout; it is never worth anabort().src/commands/picker/pager.rsandsrc/commands/picker/prs.rsusewait_timeouttoo and presumably have the same exposure.Environment
extra);wait-timeout0.2.1 is still inCargo.lockon v0.74.0, andsrc/shell_exec.rsstill uses it, so I believe currentmainis affectedsandbox_mode = "workspace-write"; thesendtodeny is tracked at Restricted Linux sandbox breaks connected Unix socket sends and asyncio wakeups openai/codex#33793Filed by Claude Opus 5 via Claude Code.