# How Subprocess Execution Works in Deno: A Deep Dive into Deno.Command and the ext/process Extension

> Understand Deno subprocess execution with Deno Command. Learn how Deno manages permissions, builds platform-specific commands in Rust, and handles stdio for async child processes.

- Repository: [Deno/deno](https://github.com/denoland/deno)
- Tags: deep-dive
- Published: 2026-02-26

---

**Deno executes subprocesses through the `Deno.Command` API, which validates permissions, constructs platform-specific command objects in Rust, and manages stdio pipes via the `ext/process` extension before spawning async child processes.**

Subprocess execution in Deno is handled by the `ext/process` Rust extension and exposed to JavaScript through the modern `Deno.Command` API. This architecture replaces the deprecated `Deno.run` with a secure, cross-platform approach that integrates with Tokio's async runtime. Understanding how Deno spawns child processes requires examining the permission model, platform-specific command construction, and resource management implemented in the `denoland/deno` repository.

## The Modern Deno.Command API

The `Deno.Command` class serves as the primary interface for subprocess execution in Deno, offering a more robust replacement for the legacy `Deno.run` API. When instantiated, it prepares a command configuration that will later be consumed by Rust ops to spawn the actual process.

### Permission Validation with --allow-run

Before any subprocess execution begins, Deno validates permissions through `PermissionsContainer::check_run_all` in [`ext/process/lib.rs`](https://github.com/denoland/deno/blob/main/ext/process/lib.rs). The system verifies that the caller possesses `--allow-run` permission for the resolved executable path. Additionally, if the environment contains `LD_` or `DYLD_` variables that could enable binary injection attacks, Deno blocks the spawn unless those variables are explicitly allowed. This security logic is implemented in the `check_run_permission` function (lines 540-578 of [`ext/process/lib.rs`](https://github.com/denoland/deno/blob/main/ext/process/lib.rs)).

### Environment and Working Directory Resolution

Deno constructs a deterministic environment for the child process through the `compute_run_env` function, which handles environment variable inheritance and overrides. The working directory is resolved via `resolve_path` to ensure it exists before spawning. These preparations ensure that subprocess execution occurs in a predictable, sandboxed context regardless of the parent process state.

## Platform-Specific Command Construction

Deno adapts its subprocess execution strategy based on the target platform, utilizing different Rust crates and system APIs for Unix and Windows.

### Unix Implementation with Tokio

On Unix systems, Deno wraps the standard library's `std::process::Command` with `tokio::process::Command` to enable asynchronous subprocess execution. This integration allows the Tokio runtime to manage process lifecycles without blocking the JavaScript event loop. The `Command` object is configured with the resolved arguments, environment, and working directory before spawning.

### Windows Subprocess Handling

Windows subprocess execution in Deno relies on the dedicated `deno_subprocess_windows::Command` crate, implemented in [`runtime/subprocess_windows/src/process.rs`](https://github.com/denoland/deno/blob/main/runtime/subprocess_windows/src/process.rs). This specialized implementation handles Windows-specific flags including detached processes, verbatim arguments, console window hiding, and job object management. The Windows command builder provides fine-grained control over process creation flags that are unavailable in the standard Rust `Command` abstraction.

## Stdio Management and Resource Allocation

Subprocess execution in Deno requires careful management of standard input, output, and error streams, which are handled through the resource table system.

### Pipe Creation and Resource IDs

Each stdio descriptor (`stdin`, `stdout`, `stderr`, and extra file descriptors) is converted from the JavaScript `Stdio` enum to a native `StdStdio` via the `as_stdio` function. When pipes are requested, Deno creates bidirectional pipe pairs using `deno_io::bi_pipe_pair_raw` and stores the resource IDs in the internal resource table. This allows JavaScript to read from and write to the child process streams using standard Deno I/O APIs without blocking the event loop.

### IPC Support for Node.js Compatibility

When the `ipc` option is enabled, Deno creates an additional communication pipe and registers either an `IpcJsonStreamResource` or `IpcAdvancedStreamResource`. The file descriptor number is passed to the child via the `NODE_CHANNEL_FD` environment variable, enabling Node.js-compatible inter-process communication. The child process can then communicate over this channel using the standard Node.js `child_process` API, implemented in [`ext/node/polyfills/internal/child_process.ts`](https://github.com/denoland/deno/blob/main/ext/node/polyfills/internal/child_process.ts).

## Spawning, Waiting, and Killing Processes

The lifecycle of subprocess execution in Deno is managed through a series of Rust ops that handle spawning, synchronization, and termination.

### Async Process Spawning (op_spawn_child)

The `op_spawn_child` operation constructs the platform-specific `Command` object (as defined in [`ext/process/lib.rs`](https://github.com/denoland/deno/blob/main/ext/process/lib.rs) lines 410-511) and invokes `command.spawn()`. This creates a child process and wraps the returned `Child` struct in a `ChildResource` that implements Deno's `Resource` trait. The child's PID is stored for subsequent `kill` or `wait` operations, and the resource is tracked in the runtime's resource table.

### Synchronous Output Collection (op_spawn_sync)

For synchronous subprocess execution, `op_spawn_sync` builds the same `Command` configuration but immediately calls `wait_with_output()`. This collects stdout and stderr into a `SpawnOutput` struct, blocking the JavaScript thread until the process completes. This operation powers the `Deno.Command.outputSync()` method when called from JavaScript.

### Process Termination (op_spawn_kill)

Process termination is handled by `op_spawn_kill`, which forwards to platform-specific implementations. On **Unix**, Deno uses `nix::sys::signal::kill` to send POSIX signals to the process. On **Windows**, it invokes `deno_subprocess_windows::process_kill` from [`runtime/subprocess_windows/src/process.rs`](https://github.com/denoland/deno/blob/main/runtime/subprocess_windows/src/process.rs), which ultimately calls the Win32 `TerminateProcess` API. This ensures reliable cross-platform process termination while respecting OS-specific signal semantics.

## Key Source Files in the Deno Repository

Understanding subprocess execution in Deno requires familiarity with these specific files in the `denoland/deno` repository:

- **[`ext/process/lib.rs`](https://github.com/denoland/deno/blob/main/ext/process/lib.rs)** – Core implementation of `Deno.Command` ops including `op_spawn_child`, `op_spawn_wait`, `op_spawn_sync`, permission handling (`check_run_permission`), and environment resolution (`compute_run_env`).

- **[`runtime/subprocess_windows/src/process.rs`](https://github.com/denoland/deno/blob/main/runtime/subprocess_windows/src/process.rs)** – Windows-specific process creation and termination logic, including `process_kill`, job object handling, and console window management.

- **[`ext/node/ops/process.rs`](https://github.com/denoland/deno/blob/main/ext/node/ops/process.rs)** – Node.js compatibility layer that forwards `child_process` operations to the underlying Deno subprocess implementation.

- **[`ext/node/polyfills/internal/child_process.ts`](https://github.com/denoland/deno/blob/main/ext/node/polyfills/internal/child_process.ts)** – JavaScript polyfill mapping the Node.js `child_process` API to Deno's `Command` implementation.

- **`cli/tsc/dts/node/child_process.d.cts`** – TypeScript definitions exposing the `child_process` API to TypeScript users.

## Summary

Subprocess execution in Deno follows a rigorous security and resource management model:

- **Permission-gated spawning** – Every subprocess requires explicit `--allow-run` permission, with additional checks for dangerous environment variables like `LD_` and `DYLD_` that could enable binary injection.

- **Platform abstraction** – Deno uses `tokio::process::Command` on Unix and a custom `deno_subprocess_windows` crate on Windows to handle OS-specific process creation flags and termination APIs.

- **Resource-managed I/O** – Stdio pipes are created using `deno_io::bi_pipe_pair_raw` and tracked via Deno's resource table, enabling async I/O without blocking the JavaScript event loop.

- **Unified lifecycle management** – The `op_spawn_child`, `op_spawn_wait`, `op_spawn_sync`, and `op_spawn_kill` ops provide consistent async spawning, synchronous output collection, and cross-platform process termination.

## Frequently Asked Questions

### What is the difference between Deno.Command and the deprecated Deno.run?

`Deno.Command` is the modern API for subprocess execution in Deno, offering a more ergonomic interface with separate `spawn()`, `output()`, and `outputSync()` methods. Unlike the deprecated `Deno.run`, which returned a `Process` object immediately, `Deno.Command` requires explicit permission checks before construction and provides better resource management through the `Child` resource system implemented in [`ext/process/lib.rs`](https://github.com/denoland/deno/blob/main/ext/process/lib.rs).

### How does Deno handle permissions for subprocess execution?

Before spawning any subprocess, Deno validates permissions through `PermissionsContainer::check_run_all` in [`ext/process/lib.rs`](https://github.com/denoland/deno/blob/main/ext/process/lib.rs). The caller must have `--allow-run` permission for the specific executable path. Additionally, Deno scans environment variables for `LD_` or `DYLD_` prefixes that could enable library injection attacks and blocks execution unless these variables are explicitly allowed, as implemented in the `check_run_permission` function.

### Can Deno subprocesses communicate via IPC?

Yes, Deno supports inter-process communication through the `ipc` option in `Deno.Command`. When enabled, Deno creates an additional pipe and registers an `IpcJsonStreamResource` or `IpcAdvancedStreamResource`, passing the file descriptor to the child via the `NODE_CHANNEL_FD` environment variable. This enables Node.js-compatible IPC using the `child_process` module polyfill located in [`ext/node/polyfills/internal/child_process.ts`](https://github.com/denoland/deno/blob/main/ext/node/polyfills/internal/child_process.ts).

### How does process killing work on Windows vs Unix in Deno?

Deno implements platform-specific process termination in `op_spawn_kill`. On Unix systems, Deno uses `nix::sys::signal::kill` to send POSIX signals to the process. On Windows, Deno invokes `deno_subprocess_windows::process_kill` from [`runtime/subprocess_windows/src/process.rs`](https://github.com/denoland/deno/blob/main/runtime/subprocess_windows/src/process.rs), which ultimately calls the Win32 `TerminateProcess` API. This ensures reliable cross-platform process termination while respecting OS-specific signal semantics.