Oam.js v0.9.0
Oam.js 0.9.0 is a child_process release. The module had
no differential coverage against Node until now, so nearly everything
on a failure or option-edge path was untested — most of what follows
was found by reading it properly for the first time, not by anything
failing.
The behavior changes
-
stdiois honored at all now.child_processignored thestdiooption entirely —'inherit'and'ignore'both behaved as'pipe'. A child's output went into pipes the parent never forwarded and its stdin was a pipe nobody fed. That broke any launcher script that hands its own stdio to a grandchild — the shape every npmbinshim uses: an MCP sidecar started through one booted and then sat mute forever, with the launcher still reporting success.'inherit'is now a real OS-level handle hand-off — nothing is copied through the parent. -
execFile()no longer runs through a shell. It joined argv into one string and handed it toexec(), so arguments were re-split on whitespace and shell metacharacters inside an argument were executed. Node'sexecFileis shell-free by design; oam's now passes argv verbatim too. -
spawnSync()reportsENOBUFSwhere it used to truncate atmaxBufferand report success. A short result looked complete — a half JSON document that still parses — with nothing for a Node-shaped check to branch on. -
exec()andexecFile()enforcetimeout. The option was accepted and ignored, so a child that hung hung the caller forever; now it is killed and the callback reportskilled: truewith the signal. -
fork()and a misplaced'ipc'slot now throw.fork()accepted an explicitstdioarray with no'ipc'entry — Node throwsERR_CHILD_PROCESS_IPC_REQUIRED— so code written and tested here was fatal on Node. An'ipc'slot anywhere but last silently renumbered the child's fds; it now throwsERR_INVALID_ARG_VALUE. -
fs.openSyncdescriptors start at 64 rather than 3. The counter started exactly where a launcher's inherited fd 3 lands, so oam's firstopenSynccould shadow a parent-handed descriptor. An unknown low fd is now unambiguously "the parent gave me this".
Both halves of extra-fd spawns
oam can now be the child of an extra-fd spawn, not only the parent
of one. A descriptor the parent hands us is adopted the first time
an fs call names it, so readSync(3, …) and
writeSync(4, …) behave as on Node instead of throwing
EBADF, and closeSync(4) closes
the parent's descriptor so the peer sees EOF instead of hanging.
This is the receive half of the CDP pipe transport.
A numbered descriptor in a stdio slot is honored too.
stdio: ['ignore', logFd, logFd] — the
daemonize-into-a-logfile shape — now hands the child a dup so the
output reaches the file; it used to collapse to
'inherit' while child.stdout === null made
the redirect look like it had worked.
The failure paths
-
spawn()spliced the'ipc'entry out of the caller'soptions.stdioin place — reusing one options literal for a worker pool gave the first child a channel and every later one none. -
kill()was a silent no-op while the native handle was resolving. It returnedtruewhile the child kept running; the signal is now held until the handle lands. -
spawnSync()'stimeoutreturnedETIMEDOUTwithout killing the child, which ran on holding its port and its lock. Fixed. -
A failed
spawn()now produces Node's error shape —spawn <cmd> ENOENTwith.code,.syscalland.path— whatexeca,cross-spawnandwhich-style resolvers branch on. It also emits'close'after'error'; a failure with an'ipc'slot no longer hangs the process; and writing to a failed child's stdin no longer kills it. -
fork()no longer swallows a non-silent child's output — Node inherits stdio unlesssilent: true; oam piped and dropped it, soconsole.logfrom a forked child vanished. -
exec()'smaxBuffercovered stdout only and was measured quadratically — gigabytes of copying at the 50MB default. Overflow now reportsERR_CHILD_PROCESS_STDIO_MAXBUFFER.
One release-process fix: the MCP sidecar matrix ran after the GitHub release went live — a "do not ship" verdict on a build the installer could already resolve. It now runs before the release is cut.
Install or update
macOS and Linux:
curl -fsSL https://oamjs.org/install.sh | sh
Windows (PowerShell):
irm https://oamjs.org/install.ps1 | iex
To pin this release, put OAM_VERSION on
sh, not on curl:
curl -fsSL https://oamjs.org/install.sh | OAM_VERSION=v0.9.0 sh
$env:OAM_VERSION = "v0.9.0"; irm https://oamjs.org/install.ps1 | iex
An existing install updates itself:
oam self-update
which re-runs the installer with the same verification. Binaries are
unsigned; the installer checks them against the published
SHA256SUMS and refuses a mismatch. Direct downloads and
checksums: the downloads page.