{"record":{"id":"2e29016757c0072a","repo":"google/zx","slug":"cannot-pipe-to-a-settled-process","errorCode":null,"errorMessage":"Cannot pipe to a settled process.","messagePattern":"Cannot pipe to a settled process\\.","errorType":"exception","errorClass":"Fail","httpStatus":null,"severity":"error","filePath":"src/core.ts","lineNumber":654,"sourceCode":"    return this\n  }\n\n  // prettier-ignore\n  private _pipe(source: keyof TSpawnStore, dest: PipeDest, ...args: any[]): PromisifiedStream | ProcessPromise {\n    if (isString(dest))\n      return this._pipe(source, fs.createWriteStream(dest))\n\n    if (isStringLiteral(dest, ...args))\n      return this._pipe(\n        source,\n        $({\n          halt: true,\n          signal: this.signal,\n        })(dest as TemplateStringsArray, ...args)\n      )\n\n    const isP = dest instanceof ProcessPromise\n    if (isP && dest.isSettled()) throw new Fail('Cannot pipe to a settled process.')\n    if (!isP && dest.writableEnded) throw new Fail('Cannot pipe to a closed stream.')\n\n    this._piped = true\n    ProcessPromise.bus.pipe(this, dest)\n\n    const { ee } = this._snapshot\n    const output = this.output\n    const from = new VoidStream()\n    const check = () => !!ProcessPromise.bus.refs.get(this)?.has(dest)\n    const end = () => {\n      if (!check()) return\n      setImmediate(() => {\n        ProcessPromise.bus.unpipe(this, dest)\n        ProcessPromise.bus.sources(dest).length === 0 && from.end()\n      })\n    }\n    const fill = () => {\n      for (const chunk of this._zurk!.store[source]) from.write(chunk)","sourceCodeStart":636,"sourceCodeEnd":672,"githubUrl":"https://github.com/google/zx/blob/00a2c484e219c2e84bfc3a199febf7fbce2cfbf4/src/core.ts#L636-L672","documentation":"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.","triggerScenarios":"`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.","commonSituations":"Building a pipeline after one stage already completed; awaiting a stage then trying to pipe into it; reusing ProcessPromise instances across pipelines.","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."],"exampleFix":"// before\nconst dest = $`cat`\nawait dest\nsrc.pipe(dest)\n// after\nsrc.pipe($({ halt: true })`cat`)","handlingStrategy":"validation","validationCode":"import { ProcessPromise } from 'zx'\n\nfunction pipeIfLive(src: ProcessPromise, dest: ProcessPromise): unknown {\n  if (dest.isSettled?.() ?? false) {\n    throw new Error('destination ProcessPromise already settled; use a fresh/halted one')\n  }\n  return src.pipe(dest as any)\n}","typeGuard":"const isUnsettledProcess = (d: unknown): d is ProcessPromise =>\n  d instanceof ProcessPromise && !(d as any).isSettled?.()","tryCatchPattern":"try {\n  src.pipe(dest)\n} catch (e) {\n  if (e instanceof Fail && /Cannot pipe to a settled process/.test(e.message)) {\n    src.pipe($({ halt: true })`cat`)\n  } else throw e\n}","preventionTips":["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."],"tags":["pipe","stream","lifecycle"],"backgroundTag":null,"analyzedSha":"00a2c484e219c2e84bfc3a199febf7fbce2cfbf4","analyzedAt":"2026-08-13T02:11:06.305Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}