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
- Pipe into a fresh or halted ProcessPromise so it runs on demand: `src.pipe($({halt:true})\`cat\`)`.
- Ensure the destination has not been awaited/run before pipe() is called.
- 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
- Pipe into fresh or halted ProcessPromises so they run on demand.
- Never await a destination stage before piping into it.
- If a stage is already done, pipe into a plain Writable stream instead.
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
- Too late to abort the process.
- Trying to abort a process without creating one.
- Too late to kill the process.
- Trying to kill a process without creating one.
AI-assisted analysis of google/zx@00a2c484e2 (2026-08-13).
Data as JSON: /api/errors/2e29016757c0072a.
Report an issue: GitHub.