nodejs / nodejs/node

Feature request: PTY support in `child_process.spawn()`

Đang mở
#64,019 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

feature request
Ngôn ngữ chính
JavaScript
Star
122k
Fork
37.3k
Merge trung bình
4 ngày 2 giờ
Pull request đã merge (30 ngày)
283

Mô tả

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.isTTY is 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 -eL only affects some libc-buffered programs; grandchild processes
    (e.g. make jobs) may still block-buffer.
  • script -q -f allocates a PTY but adds Script started/done noise 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:

  1. The terminal shows almost nothing for minutes ("stuck" UX).
  2. The log file updates in large bursts, not line-by-line.
  3. 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 data events; 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

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

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu từ điểm vào child_process.spawn() và xem xét test/pseudo-tty/pty_helper.py, sau đó kiểm tra issue và PR của libuv được tham chiếu vì yêu cầu này đang bị chặn bởi hỗ trợ PTY của libuv. Để hoàn tất sẽ cần một API được thống nhất với hành vi POSIX và ConPTY đa nền tảng, bao gồm xử lý stream, resize, fallback và spawnSync.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
javascript, node.js
Lĩnh vực
api, backend
Loại issue
Tính năng
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Ít trao đổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
32/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.