denoland/deno · error · Error
ERR_IPC_ONE_PIPE
ERR_IPC_ONE_PIPE
Error message
Child process can have only one IPC pipe
What it means
ERR_IPC_ONE_PIPE is thrown when the normalized stdio array contains the 'ipc' entry more than once. A child process can have exactly one IPC channel; each 'ipc' slot maps to the single kIpc option handed to the Rust spawn layer, so a second one is rejected before process creation.
Source
Thrown at ext/node/polyfills/internal/child_process.ts:435
const argv0 = args.length > 0 ? args[0] : command;
const builtCommand = buildCommand(
command,
ArrayPrototypeSlice(args, 1),
env,
);
const cmd = builtCommand[0];
const cmdArgs = builtCommand[1];
const includeNpmProcessState = builtCommand[2];
this.spawnfile = cmd;
this.spawnargs = [cmd, ...new SafeArrayIterator(cmdArgs)];
const ipc = ArrayPrototypeIndexOf(normalizedStdio, "ipc");
if (
ipc !== -1 &&
ArrayPrototypeIndexOf(normalizedStdio, "ipc", ipc + 1) !== -1
) {
throw new ERR_IPC_ONE_PIPE();
}
const extraStdioOffset = 3; // stdin, stdout, stderr
const extraStdioNormalized = [];
for (let i = 0; i < extraStdio.length; i++) {
const fd = i + extraStdioOffset;
if (fd === ipc) {
// IPC fd is handled separately in Rust via the kIpc option.
// Push a placeholder so the array indices stay aligned with
// fd numbers, but don't double-push.
ArrayPrototypePush(extraStdioNormalized, "null");
continue;
}
ArrayPrototypePush(extraStdioNormalized, toDenoStdio(extraStdio[i]));
}
// Windows does not support uid/gid options - throw ENOTSUP synchronously.View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Include 'ipc' exactly once in the stdio array, e.g. ['ipc', 'pipe', 'pipe']
- Prefer fork() which manages the IPC entry for you — don't also hand it a manual 'ipc'
- Dedupe before spawn: new Set(stdio).has('ipc') count check, keep only the first occurrence
Example fix
// before
spawn(process.execPath, [script], { stdio: ['ipc', 'pipe', 'pipe', 'ipc'] });
// after
spawn(process.execPath, [script], { stdio: ['ipc', 'pipe', 'pipe'] }); Defensive patterns
Strategy: validation
Validate before calling
function withSingleIpc(stdio) {
const first = stdio.indexOf('ipc');
if (first === -1) return stdio;
if (stdio.indexOf('ipc', first + 1) !== -1) {
throw new Error('stdio may contain at most one \'ipc\' entry');
}
return stdio;
} Type guard
const hasSingleIpc = (stdio) => stdio.filter((s) => s === 'ipc').length <= 1;
Try / catch
try { spawn(cmd, args, { stdio }); } catch (err) {
if (err?.code === 'ERR_IPC_ONE_PIPE') {
const first = stdio.indexOf('ipc');
spawn(cmd, args, { stdio: stdio.map((s, i) => s === 'ipc' && i !== first ? 'pipe' : s) });
}
} Prevention
- Let fork() own the IPC entry; don't add 'ipc' yourself on top of it
- Keep 'ipc' at one slot (conventionally index 0 or 3+), never repeated
- When merging user stdio with defaults, dedupe 'ipc' before spawning
When it happens
Trigger: spawn(cmd, args, { stdio: ['ipc', 'ipc', 'pipe'] }); manually adding 'ipc' to stdio while also passing options.serialization/stdio handling that already includes it; fork()-style usage where a channel is appended on top of a stdio array that already contains 'ipc'.
Common situations: Copy-pasting a fork stdio preset like ['ipc', 'pipe', 'pipe', 'ipc'] (an extra entry for fd 3); merging user stdio with IPC defaults via array concat producing duplicates; upgrading code from spawn() to fork() without removing the manual 'ipc' entry.
Related errors
- ERR_INVALID_ARG_VALUE
- ERR_IPC_SYNC_FORK
- ERR_CHILD_PROCESS_IPC_REQUIRED
- ERR_INVALID_SYNC_FORK_INPUT
- ERR_INVALID_ARG_TYPE
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/8ed0877dad4749a1.
Report an issue: GitHub.