google/zx · error · Fail

Cannot pipe to a settled process.

Error message

Cannot pipe to a settled process.

What it means

Thrown by ProcessPromise._pipe() when the destination is a ProcessPromise that has already settled (isSettled() true). Piping into a finished process would silently drop data, so zx rejects up front.

Source

Thrown at src/core.ts:654

    return this
  }

  // prettier-ignore
  private _pipe(source: keyof TSpawnStore, dest: PipeDest, ...args: any[]): PromisifiedStream | ProcessPromise {
    if (isString(dest))
      return this._pipe(source, fs.createWriteStream(dest))

    if (isStringLiteral(dest, ...args))
      return this._pipe(
        source,
        $({
          halt: true,
          signal: this.signal,
        })(dest as TemplateStringsArray, ...args)
      )

    const isP = dest instanceof ProcessPromise
    if (isP && dest.isSettled()) throw new Fail('Cannot pipe to a settled process.')
    if (!isP && dest.writableEnded) throw new Fail('Cannot pipe to a closed stream.')

    this._piped = true
    ProcessPromise.bus.pipe(this, dest)

    const { ee } = this._snapshot
    const output = this.output
    const from = new VoidStream()
    const check = () => !!ProcessPromise.bus.refs.get(this)?.has(dest)
    const end = () => {
      if (!check()) return
      setImmediate(() => {
        ProcessPromise.bus.unpipe(this, dest)
        ProcessPromise.bus.sources(dest).length === 0 && from.end()
      })
    }
    const fill = () => {
      for (const chunk of this._zurk!.store[source]) from.write(chunk)

View on GitHub (pinned to 00a2c484e2)

Solutions

  1. Pipe into a fresh or halted ProcessPromise so it runs on demand: `src.pipe($({halt:true})\`cat\`)`.
  2. Ensure the destination has not been awaited/run before pipe() is called.
  3. If the dest is done, pipe into a plain Writable stream (e.g. fs.createWriteStream) instead.

Example fix

// before
const dest = $`cat`
await dest
src.pipe(dest)
// after
src.pipe($({ halt: true })`cat`)
Defensive patterns

Strategy: validation

Validate before calling

import { ProcessPromise } from 'zx'

function pipeIfLive(src: ProcessPromise, dest: ProcessPromise): unknown {
  if (dest.isSettled?.() ?? false) {
    throw new Error('destination ProcessPromise already settled; use a fresh/halted one')
  }
  return src.pipe(dest as any)
}

Type guard

const isUnsettledProcess = (d: unknown): d is ProcessPromise =>
  d instanceof ProcessPromise && !(d as any).isSettled?.()

Try / catch

try {
  src.pipe(dest)
} catch (e) {
  if (e instanceof Fail && /Cannot pipe to a settled process/.test(e.message)) {
    src.pipe($({ halt: true })`cat`)
  } else throw e
}

Prevention

When it happens

Trigger: `src.pipe(dest)` where `dest` is a ProcessPromise you already awaited or that already ran to completion; reusing a completed command as a pipe target; ordering bugs where the dest was started and finished before the pipe() call.

Common situations: Building a pipeline after one stage already completed; awaiting a stage then trying to pipe into it; reusing ProcessPromise instances across pipelines.

Related errors


AI-assisted analysis of google/zx@00a2c484e2 (2026-08-13). Data as JSON: /api/errors/2e29016757c0072a. Report an issue: GitHub.