# Node.js v26.8.2: waiting for ChildProcess exit, close, and success

> Compare exit and stdio closure, define Promise success explicitly, and separate proposed ChildProcess methods from Node.js v26.8.2's documented APIs.

Canonical URL: https://www.devobs.io/articles/in-node-js-how-should-promise-based-waits-distinguish-childprocess-exit-s-fb0c616c5e5419d1/
By: Lucas Vale
Published: 2026-09-11T14:55:54.154Z
Updated: 2026-09-11T14:55:54.154Z
Section: Architecture

Choose a Promise wait by answering two questions: **which lifecycle boundary matters, and what counts as success?**

For a spawned Node.js child process, `exit` reports process termination, while `close` reports termination and stdio closure. Neither event's arrival alone establishes a successful exit status. `events.once()` converts an event wait into a Promise, but rejects if the child emits `error` while that wait is pending. That rejection is not evidence that stdio has closed. [ChildProcess lifecycle](https://nodejs.org/download/release/v26.8.2/docs/api/child_process.html#class-childprocess) · [events.once()](https://nodejs.org/download/release/v26.8.2/docs/api/events.html#eventsonceemitter-name-options)

**Reference release: Node.js v26.8.2.** The comparison below uses version-pinned documentation for that release. It does not assume that v26.8.2 is installed in your environment or that you use a particular operating system.

## Are closed(), exited(), and succeeded() available?

The [v26.8.2 ChildProcess reference](https://nodejs.org/download/release/v26.8.2/docs/api/child_process.html#class-childprocess) does not document `closed()`, `exited()`, or `succeeded()` as public methods. For this reference release, use the documented lifecycle events and `events.once()` rather than relying on those proposed method names.

[Node.js issue #59994](https://github.com/nodejs/node/issues/59994), opened on September 24, 2025, requested three distinct contracts:

| Proposed method | Requested fulfillment | Requested rejection | v26.8.2 public API status |
| --- | --- | --- | --- |
| `closed()` | Fulfill with the child process when `close` fires | Reject if `error` fires | Not documented |
| `exited()` | Fulfill with the child process when `exit` fires | Reject if `error` fires | Not documented |
| `succeeded()` | Fulfill with no value when the child closes with exit code zero | Reject otherwise | Not documented |

These are the author's proposed contracts, not shipped API documentation. September 24, 2025 is the feature-request date, not a release date. The request does not establish availability.

For another installed release, identify its exact version and check each method against that release's documentation. The v26.8.2 answer is a reference point, not a compatibility determination for an unspecified runtime.

## Exit and close mark different boundaries

Node.js v26.8.2 documents these distinctions:

- **`exit`:** the child process has ended, but its stdio streams might still be open.
- **`close`:** the process has ended and the child's stdio streams have closed. Multiple processes can share stdio streams.
- **Ordering:** `close` follows `exit`, or follows `error` if the child failed to spawn.
- **Errors:** an `error` event does not guarantee a subsequent `exit` event. [Lifecycle definitions](https://nodejs.org/download/release/v26.8.2/docs/api/child_process.html#class-childprocess)

When reporting process termination, preserve both event arguments. Ordinary exit supplies a numeric `code` and a `null` signal; signal termination supplies a `null` code and the signal name. Consequently, `if (!code)` cannot distinguish zero from `null`. Use explicit comparisons. [Exit and close arguments](https://nodejs.org/download/release/v26.8.2/docs/api/child_process.html#class-childprocess)

## Compare Promise settlement policies in v26.8.2

Here, `once` means `events.once()` from `node:events`. The first two rows use its documented contract. The third adds an application-defined success policy; it is not an implementation of a documented `ChildProcess.succeeded()` method. These rows assume no cancellation signal is passed to `events.once()`.

| Completion goal | Documented event/API | Promise settlement policy | Failure cases and limits | Primary-source citation |
| --- | --- | --- | --- | --- |
| Observe process termination | `once(child, 'exit')` | Fulfill with `[code, signal]` when `exit` arrives; reject if `error` arrives first | A nonzero code or signal does not itself cause rejection. Stdio may remain open. After an error, `exit` is not guaranteed. | [ChildProcess](https://nodejs.org/download/release/v26.8.2/docs/api/child_process.html#class-childprocess), [once](https://nodejs.org/download/release/v26.8.2/docs/api/events.html#eventsonceemitter-name-options) |
| Observe termination and stdio closure | `once(child, 'close')` | Fulfill with `[code, signal]` when `close` arrives; reject if `error` arrives first | A nonzero code or signal does not itself cause rejection. Error rejection can precede closure. | [ChildProcess](https://nodejs.org/download/release/v26.8.2/docs/api/child_process.html#class-childprocess), [once](https://nodejs.org/download/release/v26.8.2/docs/api/events.html#eventsonceemitter-name-options) |
| Require zero-status completion at close, with immediate error reporting | `close`, `events.once()`, and an explicit status check | Chosen policy: fulfill with no value only when the close wait fulfills with `code === 0` and `signal === null`; reject on another status or if the close wait rejects | Spawn and other emitted errors can reject before closure. Nonzero status and signal termination fail this policy. | [ChildProcess](https://nodejs.org/download/release/v26.8.2/docs/api/child_process.html#class-childprocess), [once](https://nodejs.org/download/release/v26.8.2/docs/api/events.html#eventsonceemitter-name-options) |

The distinction is **event fulfillment versus application success**. `events.once()` returns the event's argument array; it does not interpret the exit code. Its fulfillment value also differs from the child-process object requested for the proposed `closed()` and `exited()` methods. [once contract](https://nodejs.org/download/release/v26.8.2/docs/api/events.html#eventsonceemitter-name-options) · [Proposed contracts](https://github.com/nodejs/node/issues/59994)

Add further acceptance checks if your task requires more than a zero process status—for example, validation of the output your application needs.

## Keep failure reporting separate from closure observation

A ChildProcess `error` can report failure to spawn, failure to kill the process, failure to send a message, or abortion through the child-process `signal` option. It is not exclusively a spawn-failure notification. Node.js warns that handlers observing both `error` and `exit` must guard against invoking shared handler logic multiple times. [Error cases and safeguard](https://nodejs.org/download/release/v26.8.2/docs/api/child_process.html#class-childprocess)

For a wrapper, decide separately:

1. **Failure reporting:** should an emitted error reject immediately?
2. **Closure observation:** must the caller still know when stdio has closed?

If both matter, keep an independent callback listener for `close` active after immediate rejection, and maintain an error observer throughout the observation period. Record errors separately and guard shared finalization side effects against duplicate execution. A rejected `once(child, 'close')` cannot substitute for observing `close`: rejection may have come from `error` instead. [Lifecycle semantics](https://nodejs.org/download/release/v26.8.2/docs/api/child_process.html#class-childprocess) · [once error handling](https://nodejs.org/download/release/v26.8.2/docs/api/events.html#eventsonceemitter-name-options)

There is one important exception to this error-rejection behavior: `once(child, 'error')` fulfills with the error event's arguments. Special error rejection applies when waiting for another event. [Waiting for error itself](https://nodejs.org/download/release/v26.8.2/docs/api/events.html#eventsonceemitter-name-options)

## Register every required wait before awaiting

Install observers and create the required Promise waits immediately after `spawn()` returns, without an intervening `await`. Do not make the close subscription depend on finishing an exit await.

The Events documentation explains the registration hazard: conventional event listeners run synchronously, while Promise continuations run after the current execution stack completes. Multiple events can therefore occur before execution resumes after an `await`. It also identifies `process.nextTick()` ordering as a case where this matters. Its recommendation is to create all required Promises before awaiting any of them. This is a general event-registration hazard, not a claim that every ChildProcess run reproduces it. [Multiple-event waiting caveat](https://nodejs.org/download/release/v26.8.2/docs/api/events.html#caveats-when-awaiting-multiple-events)

When creating several waits, attach rejection handling to all of them immediately. For diagnostics, record event order inside the event callbacks, separately from when Promise handlers run.

## Unexecuted test procedure for your runtime

**This procedure has not been executed. It reports no observed outcomes or validated remedy.** Use it to examine the selected contracts on your actual Node.js version and OS.

1. **Record the environment and set a bound.** Note the exact Node.js version, OS, and absolute path to the Node.js executable. Consult matching version-pinned documentation. Choose an observation deadline and an OS-appropriate cleanup plan before running each case. If inspecting the three proposed method names at runtime, record their presence separately from any investigation of their behavior.

2. **Prepare isolated cases.** In a temporary directory, prepare a child fixture intended to write a short stdout marker and finish with status zero; another intended to write a marker and finish with status seven; and a long-running fixture that writes a readiness marker and remains active. For spawn failure, choose an absolute executable path in that directory and verify that no file exists there. Spawn directly, without a shell.

3. **Install instrumentation immediately.** Arm the deadline before spawning. As soon as `spawn()` returns a child object, synchronously register callbacks for `spawn`, `error`, `exit`, and `close`. Record event names and arguments inside those callbacks. Begin consuming stdout and stderr when present. Keep the error observer and an independent close observer active even if a Promise rejects. Record any exception from the spawn call separately, rather than inventing an event entry for it. [ChildProcess events and streams](https://nodejs.org/download/release/v26.8.2/docs/api/child_process.html#class-childprocess)

4. **Create and handle all waits before yielding.** Create `once(child, 'exit')` and `once(child, 'close')`. Derive a third Promise from the close wait using the table's zero-code/no-signal policy. Immediately attach fulfillment and rejection recording handlers to each wait and use `Promise.allSettled()` to collect settlements. Keep the deadline independent of that aggregate wait. Record Promise settlements separately from the callback event log. [Promise registration guidance](https://nodejs.org/download/release/v26.8.2/docs/api/events.html#caveats-when-awaiting-multiple-events)

5. **Run each case separately.** Run the zero-status, nonzero-status, and nonexistent-executable cases. For signal termination, request an OS-supported termination signal only after capturing the long-running fixture's complete readiness marker. Record the request's result, any thrown or emitted error, and the actual termination arguments. If unsupported, record a skip and its reason. Do not equate sending a signal with observing termination. [Signal-delivery caveats](https://nodejs.org/download/release/v26.8.2/docs/api/child_process.html#class-childprocess)

6. **Observe closure and clean up.** Continue independent closure observation even if all Promise waits have already rejected. If the deadline expires, record pending waits and the case as incomplete, then carry out cleanup. If using a separate `AbortSignal` with `events.once()` to cancel pending waits, label those settlements as wait cancellations—not observations of process closure. Keep lifecycle monitoring active during cleanup, then remove the test observers. [Wait cancellation](https://nodejs.org/download/release/v26.8.2/docs/api/events.html#eventsonceemitter-name-options)

Populate this empty template only from actual runs:

| Node.js version | OS | case | event sequence | code/signal | Promise settlement | notes |
| --- | --- | --- | --- | --- | --- | --- |

Keep the implementation decision explicit: **which event ends the wait, which status counts as success, and whether failure reporting must remain separate from closure observation**. Verify convenience methods against your release rather than inferring availability or behavior from their proposed names.

## Source references

- <https://nodejs.org/download/release/v26.8.2/docs/api/child_process.html#class-childprocess>
- <https://nodejs.org/download/release/v26.8.2/docs/api/events.html#eventsonceemitter-name-options>
- <https://nodejs.org/download/release/v26.8.2/docs/api/events.html#caveats-when-awaiting-multiple-events>
- <https://github.com/nodejs/node/issues/59994>
