Feature request: PTY support in `child_process.spawn()`
Nadie ha tomado este issue todavía.
- Lenguaje dominante
- JavaScript
- Estrellas
- 122k
- Forks
- 37.3k
- Merge medio
- 4 d 2 h
- PR fusionados (30 d)
- 283
Descripción
What is the problem this feature will solve?
Please add first-class pseudo-terminal (PTY) support to child_process.spawn()
(and ideally spawnSync()), so child processes see a real TTY on stdout/stderr/stdin
instead of pipes.
Today, the only practical option is third-party native addons such as
node-pty. That works, but it adds
native compilation, Electron/ABI friction, and an extra dependency for a capability
that shells and other runtimes expose natively.
Built-in PTY support would make Node.js a better cross-platform choice for
developer tools, CI runners, test harnesses, and build orchestrators compared
with wrapping PowerShell or Python just to get a TTY.
Problem
When spawn() uses the default stdio: ['pipe', 'pipe', 'pipe'], the child's
stdout/stderr are not TTYs. Many programs behave differently:
- Block buffering instead of line buffering (bash, make, gcc, pacman, etc.)
isatty()/process.stdout.isTTYis false (no ANSI colors, different log format)- Interactive prompts break (password prompts,
read, pagers) - Progress output is delayed until buffers fill or the process exits
Workarounds are incomplete:
stdbuf -oL -eLonly affects some libc-buffered programs; grandchild processes
(e.g.makejobs) may still block-buffer.script -q -fallocates a PTY but addsScript started/donenoise to logs.- Redirecting to a file (
>log.txt) inside the shell has the same non-TTY behavior.
Concrete use case
I maintain a cross-platform bootstrap/build runner (Windows + MSYS/Cygwin) written
in TypeScript/Node.js because it is faster and simpler to ship than PowerShell or
Python for this use case.
The runner uses spawn(bash, ['--login', '-c', script]) and tees output to both
the terminal and a log file. Without a PTY:
- The terminal shows almost nothing for minutes ("stuck" UX).
- The log file updates in large bursts, not line-by-line.
- Some toolchain steps change behavior under non-TTY conditions.
We currently wrap commands in stdbuf -oL -eL and manually tee data events.
That helps a little but is fragile and platform-dependent. A PTY would fix the
root cause.
Minimal desired behavior:
import { spawn } from 'node:child_process';
import { createWriteStream } from 'node:fs';
const log = createWriteStream('build.log');
const child = spawn('bash', ['--login', '-c', 'make -j8'], {
cwd: repoRoot,
env: process.env,
stdio: ['pipe', 'pty', 'pty'], // or a dedicated option; see below
});
child.on('data', (chunk) => {
log.write(chunk);
process.stdout.write(chunk);
});
The child should believe it has a terminal (isatty(1) === true), use
line-oriented output, and flush promptly.
Why not node-pty?
node-pty is widely used (VS Code, etc.)
and I am grateful it exists, but for application-level build/CI tooling it has
drawbacks:
- Native addon: requires node-gyp/prebuilds; breaks or needs rebuilds across
Node/Electron ABI changes. - Extra dependency for something that feels like core process/spawn behavior.
- API divergence from
child_process.spawn()(different return type, resize
events, etc.).
For terminal emulators, node-pty is fine. For "run this shell script and stream
output live to console + log", PTY belongs in core next to spawn.
Proposed API (sketch)
Option A - extend stdio:
spawn(cmd, args, {
stdio: ['pipe', 'pty', 'pty'],
pty: {
cols: process.stdout.columns ?? 80,
rows: process.stdout.rows ?? 24,
name: 'xterm-256color',
},
});
Option B - dedicated flag:
spawn(cmd, args, {
pty: true,
stdio: ['pipe', 'pipe', 'pipe'],
});
Requirements:
- Works on Linux, macOS, Windows 10+ (ConPTY).
- Document graceful failure on older Windows (throw or fallback to pipes).
- Optional
resize(cols, rows)on the child handle. - PTY stream emits
dataevents; stdin remains writable for automation. - Document interaction with
shell: true,detached,windowsHide.
Platform notes
- POSIX:
forkpty(3)/ openpty + session setup (Node already has test helpers in
test/pseudo-tty/pty_helper.py). - Windows:
CreatePseudoConsole()(ConPTY). Older Windows may need documented
unsupported behavior rather than winpty-level emulation in core.
Prior art / related issues
- libuv feature request: https://github.com/libuv/libuv/issues/2640
- libuv PTY implementation PR: https://github.com/libuv/libuv/pull/4802
- Node issue (closed, blocked on libuv): https://github.com/nodejs/node/issues/46105
- Related: https://github.com/nodejs/node/issues/31409
This request is blocked on libuv PTY landing first; once libuv exposes
spawn-with-PTY, please expose it through child_process.
Why this matters for Node.js
Node is already the default for cross-platform CLI tooling (yarn, vite, eslint,
etc.). Live, faithful subprocess output is a basic expectation for:
- build systems and monorepo orchestrators
- test runners
- dev environment bootstrap scripts
- CI log streaming
Without PTY, authors either accept broken UX, add native deps, or shell out to
PowerShell/Python/bash script hacks. Native PTY would close that gap and reduce
reliance on node-pty for non-terminal-emulator use cases.
What is the feature you are proposing to solve the problem?
Add optional pseudo-terminal (PTY) support to child_process.spawn() (and
spawnSync()) so a spawned child can use a real TTY instead of pipes.
Proposed API (either shape is fine):
spawn(cmd, args, {
stdio: ['pipe', 'pty', 'pty'],
pty: { cols, rows, name: 'xterm-256color' },
});
or:
spawn(cmd, args, { pty: true, stdio: ['pipe', 'pipe', 'pipe'] });
Behavior:
- Child sees isatty(stdout/stderr) === true (line-buffered output, colors,
interactive prompts work as in a real terminal). - Parent gets a readable/writable PTY stream (emit 'data', write stdin).
- Optional child.resize(cols, rows) for terminal size changes.
- Linux/macOS via POSIX PTY; Windows 10+ via ConPTY; document unsupported
fallback on older Windows.
This should be implemented on top of libuv PTY support (libuv#2640,
libuv PR#4802) and exposed through child_process, similar to how pipe
and inherit stdio modes work today.
What alternatives have you considered?
No response
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Línea de trabajo
Empieza en el punto de entrada child_process.spawn() y revisa test/pseudo-tty/pty_helper.py; después, comprueba el issue y el PR de libuv referenciados, porque la solicitud está bloqueada por la compatibilidad de libuv con PTY. Para darlo por terminado, sería necesaria una API acordada con un comportamiento POSIX y ConPTY multiplataforma, incluido el manejo de streams, resize, fallback y spawnSync.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- javascript, node.js
- Área
- api, backend
- Tipo de issue
- Nueva funcionalidad
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Estado de actividad
- Tranquilo
- Claridad
- Bastante claro
- Aptitud para principiantes
- 32/100