How to Use Bun.spawn for Child Process Management in Bun

Bun.spawn is Bun's low-level API for creating and controlling child processes that returns a Process object with PID, stdio streams, and lifecycle hooks, built on top of posix_spawn or Windows CreateProcess.

Bun.spawn provides a high-performance, cross-platform interface for spawning child processes in the Bun JavaScript runtime. As implemented in the oven-sh/bun repository, this API wraps platform-specific system calls to give developers fine-grained control over process execution, streaming I/O, and inter-process communication without the overhead of traditional Node.js child_process wrappers.

Architecture of Bun.spawn

Understanding how Bun.spawn works under the hood helps optimize process management and debug complex spawning scenarios.

Public JavaScript API

The entry point Bun.spawn(argv, options?) acts as a thin wrapper that constructs the argument vector and environment array before forwarding them to the native implementation. This layer is documented in docs/guides/process/spawn.mdx and handles the initial validation of options like cwd, env, and stdio configuration.

Native Zig Implementation

In src/bun.js/api/bun/spawn.zig, the spawnProcess function selects platform-specific execution paths. For POSIX systems, spawnProcessPosix constructs a PosixSpawn.Actions list to record stdio redirections and calls posix_spawn_bun, a custom wrapper around the kernel's posix_spawn. For Windows, spawnProcessWindows utilizes the CreateProcess API to generate a WindowsSpawnResult.

Process Object and Event Loop Integration

The resulting SpawnProcessResult converts to a high-level Process struct defined in src/bun.js/api/bun/process.zig. This object tracks the PID, optional pidfd, and exit status. The Process.watch() method installs an event-loop poller—using Async.FilePoll on Linux or libuv on Windows—to monitor the child and resolve the proc.exited promise when termination occurs.

Key Configuration Options

Bun.spawn accepts an options object that controls every aspect of child process behavior before and after execution.

  • Working Directory: options.cwd triggers actions.chdir(options.cwd) in the native layer, changing the child's working directory from the parent's.
  • Detached Processes: options.detached adds the POSIX_SPAWN_SETSID flag on POSIX systems, creating a new session via setsid() so the child survives parent termination.
  • Standard I/O Configuration: options.stdin, options.stdout, and options.stderr accept "ignore", "inherit", "pipe", "buffer", or file paths. The "pipe" value creates socketpairs converted to ReadableStream or WritableStream objects.
  • Inter-Process Communication: options.ipc creates a bidirectional pipe accessible via proc.ipc, enabling message passing between parent and child.
  • Synchronous Execution: options.sync blocks the event loop until the child exits, returning the result directly rather than a Process object.
  • Process Lifecycle: The returned Process object exposes proc.pid, proc.kill(signal), and proc.exited—a Promise resolving to the exit status.

Practical Examples

Basic Process Execution

The simplest usage spawns a command and captures its output:

const proc = Bun.spawn(["/bin/echo", "Hello, Bun!"]);
const output = await proc.stdout.text();
console.log(output); // "Hello, Bun!\n"
await proc.exited;

This pattern appears in the official documentation at docs/guides/process/spawn.mdx.

Capturing stdout and stderr as Streams

For processes that write to both streams, configure "pipe" to access them as ReadableStream objects:

const proc = Bun.spawn(["node", "-e", `
  console.error("error line");
  console.log("output line");
`], {
  stdout: "pipe",
  stderr: "pipe",
});

const [out, err] = await Promise.all([
  proc.stdout.text(),
  proc.stderr.text(),
]);

console.log("STDOUT:", out);
console.log("STDERR:", err);
await proc.exited;

The native implementation in src/bun.js/api/bun/spawn.zig constructs PosixSpawn.Actions to redirect these file descriptors during the posix_spawn call.

Custom Working Directory and Environment

Control the execution context by specifying cwd and env:

const proc = Bun.spawn(["node", "-p", "process.cwd() + ':' + process.env.MY_VAR"], {
  cwd: "/tmp",
  env: { MY_VAR: "bun-rocks" },
});

const result = await proc.stdout.text();
console.log(result); // "/tmp:bun-rocks\n"
await proc.exited;

In the Zig layer, options.cwd triggers actions.chdir(options.cwd) at line 1249 of spawn.zig, while the environment array is constructed in the JavaScript wrapper before native invocation.

Running Detached Background Processes

To create a process that survives its parent, use the detached option:

Bun.spawn(["sleep", "60"], { detached: true });
console.log("Parent finished, child still running.");

This sets the POSIX_SPAWN_SETSID flag in the native implementation, creating a new session via setsid() so the child is not terminated when the parent exits.

Inter-Process Communication (IPC)

Establish a message channel between parent and child using the ipc option:

const proc = Bun.spawn(["node", "worker.js"], {
  ipc: "pipe",
});

const writer = proc.ipc!.writable.getWriter();
const reader = proc.ipc!.readable.getReader();

await writer.write("ping");
await writer.close();

const { value } = await reader.read();
console.log("Worker replied:", value);
await proc.exited;

The native layer handles options.ipc by adding an inherit action for the IPC file descriptor, making it available as a ReadableWritable pair on the resulting Process object.

Killing a Process

Send signals to terminate a child process:

const proc = Bun.spawn(["cat"], { stdin: "pipe" });
setTimeout(() => proc.kill(9), 2000); // SIGKILL after 2 seconds
await proc.exited; // Resolves when process terminates

The proc.kill(signal) method maps to std.c.kill on POSIX systems and libuv's process.kill on Windows, as implemented in src/bun.js/api/bun/process.zig.

Summary

  • Bun.spawn is the low-level API for child process creation in Bun, implemented in src/bun.js/api/bun/spawn.zig and exposed via docs/guides/process/spawn.mdx.
  • It wraps posix_spawn on POSIX systems and CreateProcess on Windows, returning a high-level Process object defined in src/bun.js/api/bun/process.zig.
  • Configure process behavior through options like cwd, env, detached, and stdio redirection (pipe, inherit, ignore).
  • Access child output via ReadableStream objects on proc.stdout and proc.stderr, or use ipc for bidirectional message passing.
  • Manage lifecycle through proc.exited (a Promise resolving to exit status), proc.kill(signal), and proc.pid.

Frequently Asked Questions

How does Bun.spawn differ from Node.js child_process.spawn?

Bun.spawn provides a unified Promise-based interface that returns a Process object with ReadableStream stdio by default, whereas Node.js child_process.spawn returns an EventEmitter with Stream objects. According to the oven-sh/bun source code in src/bun.js/api/bun/spawn.zig, Bun uses posix_spawn directly with pidfd support on Linux for efficient polling, while Node.js traditionally uses fork/exec or spawn with different stdio wiring.

Can I use Bun.spawn for synchronous process execution?

Yes, set the sync option to true to block the event loop until the child process exits. When sync: true is passed, Bun.spawn returns the process result directly rather than a Process object, similar to Node.js child_process.spawnSync. This is useful for CLI tools and build scripts where you need to wait for completion before continuing, though it prevents concurrent JavaScript execution during the process lifetime.

How do I handle large output streams without running out of memory?

Instead of using .text() which buffers the entire output, consume the ReadableStream returned by proc.stdout or proc.stderr incrementally. Use proc.stdout.getReader() to read chunks manually, or pipe the stream to a file using Bun.write(). According to the implementation in src/bun.js/api/bun/process.zig, these streams are backed by file descriptors polled by the event loop, allowing backpressure to be handled naturally without loading the entire output into the JavaScript heap.

What signals can I send with proc.kill()?

On POSIX systems, you can send any standard signal number or string such as SIGTERM (15), SIGKILL (9), SIGINT (2), or SIGHUP (1). The proc.kill(signal) method maps directly to the C library kill() function as implemented in src/bun.js/api/bun/process.zig. On Windows, the signal is translated to a termination code or uses libuv's process termination logic, supporting SIGTERM and SIGKILL equivalents, though Windows does not support POSIX signals natively.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →