{"record":{"id":"d7665e35f92346f4","repo":"google/zx","slug":"the-signal-is-controlled-by-another-process","errorCode":null,"errorMessage":"The signal is controlled by another process.","messagePattern":"The signal is controlled by another process\\.","errorType":"exception","errorClass":"Fail","httpStatus":null,"severity":"error","filePath":"src/core.ts","lineNumber":439,"sourceCode":"    ProcessPromise.bus.unpipeBack(this)\n    if (output.ok || this.isNothrow()) {\n      this._stage = 'fulfilled'\n      this._resolve(output)\n    } else {\n      this._stage = 'rejected'\n      if (legacy) {\n        this._resolve(output) // to avoid unhandledRejection alerts\n        throw output.cause || output\n      }\n      this._reject(output)\n      if (this.sync) throw output\n    }\n  }\n\n  abort(reason?: string) {\n    if (this.isSettled()) throw new Fail('Too late to abort the process.')\n    if (this.signal !== this.ac.signal)\n      throw new Fail('The signal is controlled by another process.')\n    if (!this.child)\n      throw new Fail('Trying to abort a process without creating one.')\n\n    this.ac.abort(reason)\n  }\n\n  kill(signal?: NodeJS.Signals | null): Promise<void> {\n    if (this.isSettled()) throw new Fail('Too late to kill the process.')\n    if (!this.child)\n      throw new Fail('Trying to kill a process without creating one.')\n    if (!this.pid) throw new Fail('The process pid is undefined.')\n\n    return $.kill(this.pid, signal || this._snapshot.killSignal || $.killSignal)\n  }\n\n  // Configurators\n  stdio(\n    stdin: IOType | StdioOptions,","sourceCodeStart":421,"sourceCodeEnd":457,"githubUrl":"https://github.com/google/zx/blob/00a2c484e219c2e84bfc3a199febf7fbce2cfbf4/src/core.ts#L421-L457","documentation":"Thrown by ProcessPromise.abort() when the AbortSignal in use is not zx's internal one (this.signal !== this.ac.signal). This happens when an external AbortSignal was supplied via the {signal:} option; zx cannot abort a controller it does not own, so abort() on the ProcessPromise is rejected.","triggerScenarios":"`const ac = new AbortController(); $({signal: ac.signal})\\`...\\`.abort()` — the ProcessPromise's signal is the external ac.signal, not its internal controller, so pp.abort() throws.","commonSituations":"Sharing one AbortController across multiple commands or with fetch; integrating zx into a larger abort-coordination layer; passing a request-scoped signal.","solutions":["Abort via your own controller: `ac.abort()` instead of `pp.abort()`.","Drop the custom signal option so zx manages abort internally.","Avoid sharing a single signal across processes if you need per-process abort control."],"exampleFix":"// before\nconst ac = new AbortController()\nconst pp = $({ signal: ac.signal })`sleep 10`\npp.abort()\n// after: abort the controller you own\nac.abort()","handlingStrategy":"validation","validationCode":"// If you supplied an external signal, abort THAT controller instead.\nconst ownsSignal = (pp: ProcessPromise): boolean =>\n  (pp as any).signal === (pp as any).ac.signal\n\nif (ownsSignal(pp)) pp.abort()\nelse externalController.abort()","typeGuard":null,"tryCatchPattern":"try {\n  pp.abort()\n} catch (e) {\n  if (e instanceof Fail && /signal is controlled by another process/.test(e.message)) {\n    externalController.abort() // abort the controller you own\n  } else throw e\n}","preventionTips":["When passing { signal }, always abort via the controller that owns that signal.","Do not share one AbortController across commands if you need per-command abort.","Let zx manage the signal internally unless you specifically need external coordination."],"tags":["abort","signal","abortcontroller"],"backgroundTag":null,"analyzedSha":"00a2c484e219c2e84bfc3a199febf7fbce2cfbf4","analyzedAt":"2026-08-13T02:11:06.305Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}