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

> Master Bun.spawn for child process management. Learn to create, control, and manage child processes efficiently with Bun's powerful low-level API.

- Repository: [Bun/bun](https://github.com/oven-sh/bun)
- Tags: how-to-guide
- Published: 2026-02-28

---

**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:

```typescript
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:

```typescript
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`:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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.