This page documents Bun's subprocess management system, which provides the Bun.spawn() and Bun.spawnSync() APIs for creating and managing child processes. This includes process lifecycle management, stdio redirection, IPC (inter-process communication), resource tracking, and platform-specific process spawning.
For server-side HTTP/WebSocket functionality, see Server and Networking Architecture. For lower-level socket operations, see Socket and Network Layer.
The subprocess system is implemented primarily in Zig and provides both synchronous and asynchronous process spawning. The main entry point is the Subprocess struct, which wraps the underlying platform process and manages stdio streams, IPC channels, resource usage tracking, and process lifecycle.
Sources: src/bun.js/api/bun/subprocess.zig1-100 src/bun.js/api/bun/subprocess.zig267-270
The Subprocess struct is the core data structure that represents a spawned child process. It maintains reference counting, manages process state, and coordinates between the JavaScript API surface and the underlying OS process.
Sources: src/bun.js/api/bun/subprocess.zig4-62 src/bun.js/api/bun/subprocess.zig145-193
The subprocess lifecycle is managed through a series of callbacks that handle process state transitions:
The onProcessExit callback is the central handler for process termination:
Rusage) for later retrievalexited promise or calls onExit callbackSources: src/bun.js/api/bun/subprocess.zig569-680 src/bun.js/api/bun/subprocess.zig311-358 src/bun.js/api/bun/subprocess.zig395-421
Bun provides flexible stdio handling through the Writable (stdin) and Readable (stdout/stderr) union types. Each stream can be configured as a pipe, buffer, or ignored.
The StaticPipeWriter handles writing data to the subprocess stdin. It can write from:
is_stdin_a_readable_stream flag is setSources: src/bun.js/api/bun/subprocess.zig497-532 src/bun.js/api/bun/subprocess/StaticPipeWriter.zig1-50
The PipeReader reads from stdout/stderr asynchronously:
Async.FilePoll for event-driven I/Opending (accumulating) to done (complete)Sources: src/bun.js/api/bun/subprocess.zig272-302 src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig1-100
The observable_getters tracks which stdio streams have been accessed from JavaScript, affecting reference counting and cleanup behavior.
Sources: src/bun.js/api/bun/subprocess.zig274-309 src/bun.js/api/bun/subprocess.zig402-404
Bun provides IPC capabilities for bidirectional message passing between parent and child processes. This is used when the subprocess is spawned with the ipc option enabled.
Key IPC operations:
doSend(): Sends a message to the child process via IPC.doSend()disconnect(): Closes the IPC channel, triggering onDisconnectCallbackgetConnected(): Returns whether the IPC channel is activeSources: src/bun.js/api/bun/subprocess.zig437-457 src/bun.js/api/bun/subprocess.zig36-38
Subprocesses can be killed, timed out, or aborted using various mechanisms.
Subprocesses can have a timeout configured that automatically kills the process if it runs too long:
The timeout uses event_loop_timer which is integrated with Bun's event loop timer system. The timer is reference-counted to prevent the event loop from exiting while a timeout is pending.
Sources: src/bun.js/api/bun/subprocess.zig364-390 src/bun.js/api/bun/subprocess.zig338-358 src/bun.js/api/bun/subprocess.zig44-49
When an AbortSignal is provided during spawn, it's stored and monitored. When aborted, it triggers onAbortSignal() which clears the signal reference and attempts to kill the process with the configured killSignal.
Sources: src/bun.js/api/bun/subprocess.zig111-115 src/bun.js/api/bun/subprocess.zig41-42
The subprocess uses a reference counting scheme to ensure proper cleanup:
The updateHasPendingActivity() method determines if the subprocess should keep the event loop alive based on:
Sources: src/bun.js/api/bun/subprocess.zig11-14 src/bun.js/api/bun/subprocess.zig149-179 src/bun.js/api/bun/subprocess.zig423-435
Resource usage is captured at process exit and exposed through the resourceUsage() method, which returns a JavaScript object with CPU and memory statistics.
Sources: src/bun.js/api/bun/subprocess.zig117-143 src/bun.js/api/bun/subprocess.zig86-87 src/bun.js/api/bun/subprocess.zig21-22
On POSIX systems:
bun.FileDescriptor)pipe() or pipe2()O_NONBLOCKAsync.FilePoll (epoll/kqueue)On Windows:
StdioResult which wraps handlesOVERLAPPED handles or libuv streamsSources: src/bun.js/api/bun/subprocess.zig20-21 src/bun.js/api/bun/subprocess.zig76-84
The maxBuffer option limits the amount of data buffered from stdout/stderr, preventing unbounded memory growth:
When the buffer size exceeds maxBuffer, the subprocess is immediately killed with the configured killSignal. The exited_due_to_maxbuf field tracks which stream (stdout or stderr) triggered the limit.
Sources: src/bun.js/api/bun/subprocess.zig359-362 src/bun.js/api/bun/subprocess.zig50-52
Bun supports both async (Bun.spawn()) and sync (Bun.spawnSync()) subprocess spawning:
| Feature | Async (Bun.spawn) | Sync (Bun.spawnSync) |
|---|---|---|
| Process polling | Event-driven via Async.FilePoll or libuv | Blocking wait |
| Stdio handling | Streams with async I/O | Buffered, read at end |
| Event loop | Keeps loop alive if has pending activity | Blocks event loop |
| Memory tracking | this_value uses weak ref when no pending activity | N/A (returns immediately) |
| IPC | Full bidirectional support | Not supported |
| Callbacks | onExit, onMessage, etc. | Return object only |
The is_sync flag in the Flags struct controls the behavior throughout the lifecycle.
Sources: src/bun.js/api/bun/subprocess.zig54-62 src/bun.js/api/bun/subprocess.zig615-634
When spawned with the terminal option, a subprocess can be attached to a pseudo-terminal (PTY):
When a terminal is present, the normal stdio getters (stdin, stdout, stderr) return null in JavaScript, and all I/O goes through the terminal getter instead.
Sources: src/bun.js/api/bun/subprocess.zig24-25 src/bun.js/api/bun/subprocess.zig274-309
Exit status is captured and exposed to JavaScript as a resolved or rejected promise:
Signal-based exits return 128 + signal_number to match POSIX shell conventions.
Sources: src/bun.js/api/bun/subprocess.zig569-680 src/bun.js/api/bun/subprocess.zig545-567
Refresh this wiki