google/zx · error · Fail
The signal is controlled by another process.
Error message
The signal is controlled by another process.
What it means
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.
Source
Thrown at src/core.ts:439
ProcessPromise.bus.unpipeBack(this)
if (output.ok || this.isNothrow()) {
this._stage = 'fulfilled'
this._resolve(output)
} else {
this._stage = 'rejected'
if (legacy) {
this._resolve(output) // to avoid unhandledRejection alerts
throw output.cause || output
}
this._reject(output)
if (this.sync) throw output
}
}
abort(reason?: string) {
if (this.isSettled()) throw new Fail('Too late to abort the process.')
if (this.signal !== this.ac.signal)
throw new Fail('The signal is controlled by another process.')
if (!this.child)
throw new Fail('Trying to abort a process without creating one.')
this.ac.abort(reason)
}
kill(signal?: NodeJS.Signals | null): Promise<void> {
if (this.isSettled()) throw new Fail('Too late to kill the process.')
if (!this.child)
throw new Fail('Trying to kill a process without creating one.')
if (!this.pid) throw new Fail('The process pid is undefined.')
return $.kill(this.pid, signal || this._snapshot.killSignal || $.killSignal)
}
// Configurators
stdio(
stdin: IOType | StdioOptions,View on GitHub (pinned to 00a2c484e2)
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.
Example fix
// before
const ac = new AbortController()
const pp = $({ signal: ac.signal })`sleep 10`
pp.abort()
// after: abort the controller you own
ac.abort() Defensive patterns
Strategy: validation
Validate before calling
// If you supplied an external signal, abort THAT controller instead. const ownsSignal = (pp: ProcessPromise): boolean => (pp as any).signal === (pp as any).ac.signal if (ownsSignal(pp)) pp.abort() else externalController.abort()
Try / catch
try {
pp.abort()
} catch (e) {
if (e instanceof Fail && /signal is controlled by another process/.test(e.message)) {
externalController.abort() // abort the controller you own
} else throw e
} Prevention
- 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.
When it happens
Trigger: `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.
Common situations: Sharing one AbortController across multiple commands or with fetch; integrating zx into a larger abort-coordination layer; passing a request-scoped signal.
Related errors
- Too late to abort the process.
- Trying to abort a process without creating one.
- Too late to kill the process.
AI-assisted analysis of google/zx@00a2c484e2 (2026-08-13).
Data as JSON: /api/errors/d7665e35f92346f4.
Report an issue: GitHub.