google/zx · error · Fail

Inappropriate usage. Apply $ instead of direct instantiation

Error message

Inappropriate usage. Apply $ instead of direct instantiation.

What it means

Thrown by the static disarm() trap installed on a ProcessPromise that was constructed directly via `new ProcessPromise(executor)` instead of through the `$` tagged-template factory. When the constructor receives an executor lacking the internal SHOT snapshot, it disarms every non-Promise method so any access throws, steering you toward using `$`. This prevents callers from treating ProcessPromise like a plain subclassable Promise.

Source

Thrown at src/core.ts:845

    this._stdin.write(data, encoding, cb)
    return this
  }
  private end(chunk: any, cb: any) {
    this._stdin.end(chunk, cb)
    return this
  }
  private removeListener(event: string, cb: any) {
    this._stdin.removeListener(event, cb)
    return this
  }

  // prettier-ignore
  private static disarm(p: ProcessPromise, toggle = true): void {
    Object.getOwnPropertyNames(ProcessPromise.prototype).forEach(k => {
      if (k in Promise.prototype) return
      if (!toggle) { Reflect.deleteProperty(p, k); return }
      Object.defineProperty(p, k, { configurable: true, get() {
        throw new Fail('Inappropriate usage. Apply $ instead of direct instantiation.')
      }})
    })
  }
}

type ProcessDto = {
  code: number | null
  signal: NodeJS.Signals | null
  duration: number
  error: any
  from: string
  store: TSpawnStore
  delimiter?: string | RegExp
}

export class ProcessOutput extends Error {
  private readonly _dto!: ProcessDto
  cause!: Error | null

View on GitHub (pinned to 00a2c484e2)

Solutions

  1. Always create commands via the tagged template: `` $`echo ok` `` or `` $({opts})`cmd` ``.
  2. If you need a resolved ProcessOutput, construct ProcessOutput directly rather than a fake ProcessPromise.
  3. Do not subclass or reflectively instantiate ProcessPromise.

Example fix

// before
new ProcessPromise((resolve) => resolve())
// after
$`echo ok`
Defensive patterns

Strategy: type-guard

Validate before calling

// Reject direct construction at the type layer; always route through $.
function makeCommand(tpl: TemplateStringsArray, ...args: unknown[]) {
  return $(tpl, ...args)
}

Type guard

import { ProcessPromise } from 'zx'
const madeByFactory = (p: ProcessPromise): boolean =>
  typeof (p as any).stage === 'string' // disarmed instances throw on stage access

Try / catch

try {
  // never do: new ProcessPromise(...)
  await $`echo ok`
} catch (e) {
  if (e instanceof Fail && /Apply \$ instead of direct instantiation/.test(e.message)) {
    throw new Error('Use the $ tagged template, not new ProcessPromise()')
  }
  throw e
}

Prevention

When it happens

Trigger: `new ProcessPromise((res) => res())`; subclassing or reflectively instantiating ProcessPromise; copy-pasting a Promise constructor pattern.

Common situations: Treating ProcessPromise as a normal Promise; library code that instantiates it directly; attempting to wrap or extend the class.

Related errors


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