Symptom
A cached task on macOS intermittently reports exit 2 with empty stdout and oils I/O error (main): No such process on stderr. Seen in the wild running parallel test suites that spawn vp run (each task runs under the bundled osh, which fspy substitutes for /bin/sh).
Root cause
Not fspy's exec interposition — the bundled oils 0.37.0 itself. Process::RunProcess (core/process.py) calls getpgid(child_pid) right after fork() and before waitpid(). On macOS, getpgid() of a zombie child fails with ESRCH, so when the parent is descheduled past a fast command's exit (CPU contention), the OSError escapes to oils' main() → exit 2.
Reproduces with the raw oils binary, no fspy involved: ~5% under 6-8-way parallel loops of osh -c 'echo x > f; cat f'.
Fix
Upstream PR: oils-for-unix/oils#2689 — gate the getpgid() on job control being enabled (MaybeGiveTerminal() no-ops otherwise, so batch shells never needed the pgid).
Once an oils release includes it: rebuild the bundled binary and bump MACOS_BINARY_DOWNLOADS in crates/fspy/build.rs.
Verification of the fix
Patched 0.37.0 tarball build (same one-liner in the generated C++): 2000 concurrent runs of the failing shapes, 0 failures (vs ~5% before); smoke script output byte-identical to the unpatched binary.
Symptom
A cached task on macOS intermittently reports exit 2 with empty stdout and
oils I/O error (main): No such processon stderr. Seen in the wild running parallel test suites that spawnvp run(each task runs under the bundled osh, which fspy substitutes for/bin/sh).Root cause
Not fspy's exec interposition — the bundled oils 0.37.0 itself.
Process::RunProcess(core/process.py) callsgetpgid(child_pid)right afterfork()and beforewaitpid(). On macOS,getpgid()of a zombie child fails with ESRCH, so when the parent is descheduled past a fast command's exit (CPU contention), theOSErrorescapes to oils'main()→ exit 2.Reproduces with the raw oils binary, no fspy involved: ~5% under 6-8-way parallel loops of
osh -c 'echo x > f; cat f'.Fix
Upstream PR: oils-for-unix/oils#2689 — gate the
getpgid()on job control being enabled (MaybeGiveTerminal()no-ops otherwise, so batch shells never needed the pgid).Once an oils release includes it: rebuild the bundled binary and bump
MACOS_BINARY_DOWNLOADSincrates/fspy/build.rs.Verification of the fix
Patched 0.37.0 tarball build (same one-liner in the generated C++): 2000 concurrent runs of the failing shapes, 0 failures (vs ~5% before); smoke script output byte-identical to the unpatched binary.