google/zx · error · Fail

Malformed command at ${$.from}

Error message

Malformed command at ${$.from}

What it means

Thrown by ProcessPromise.build() when at least one chunk of the tagged-template's `pieces` (the TemplateStringsArray) is null or undefined. In normal use pieces are always strings, so this guard catches programmatic or reflective invocation of `$` with an invalid pieces array rather than a genuine template literal. The message includes $.from (caller location) to pinpoint the call site.

Source

Thrown at src/core.ts:298

      this._resolve = resolve!
      this._reject = reject!
      if (snapshot.halt) this._stage = 'halted'
      try {
        this.build()
      } catch (err) {
        this.finalize(ProcessOutput.fromError(err as Error), true)
      }
    } else ProcessPromise.disarm(this)
  }
  // prettier-ignore
  private build(): void {
    const $ = this._snapshot
    if (!$.shell)
      throw new Fail(`No shell is available: ${Fail.DOCS_URL}/shell`)
    if (!$.quote)
      throw new Fail(`No quote function is defined: ${Fail.DOCS_URL}/quotes`)
    if ($.pieces.some((p) => p == null))
      throw new Fail(`Malformed command at ${$.from}`)

    $.cmd = buildCmd(
      $.quote!,
      $.pieces as TemplateStringsArray,
      $.args
    ) as string

    if ($[SYNC] && !isString($.cmd))
      throw new Fail('sync mode does not allow async command resolution')
  }
  run(): this {
    ProcessPromise.bus.runBack(this)
    if (this.isRunning() || this.isSettled()) return this // The _run() can be called from a few places.
    this._stage = 'running'

    const self = this
    const $ = self._snapshot
    const { id, cwd } = self

View on GitHub (pinned to 00a2c484e2)

Solutions

  1. Use the tagged template directly: `` $`echo hi` `` instead of constructing pieces yourself.
  2. If wrapping `$`, ensure the forwarded pieces array contains only strings (filter/null-coalesce).
  3. Inspect the {from} location in the error message to find the offending call site.
  4. Avoid reflective instantiation; use the public `$` API.
Defensive patterns

Strategy: type-guard

Validate before calling

function sanitizePieces(pieces: unknown[]): string[] {
  return pieces.map((p) => (p == null ? '' : String(p)))
}

// if you must forward to $ programmatically:
const clean = sanitizePieces(rawPieces)

Type guard

const areValidPieces = (p: unknown): p is TemplateStringsArray =>
  Array.isArray(p) && p.every((chunk) => typeof chunk === 'string')

Try / catch

import { Fail } from 'zx'
try {
  await runCustomWrapper()
} catch (e) {
  if (e instanceof Fail && /Malformed command at/.test(e.message)) {
    console.error('wrapper injected null/undefined into template pieces:', e.message)
  }
  throw e
}

Prevention

When it happens

Trigger: Calling the `$` function reflectively with a hand-built array containing null/undefined entries; a library/wrapper that intercepts and mutates the pieces array before forwarding to `$`; an edge case from a tagged-template polyfill.

Common situations: Wrapping/proxying `$` for logging or translation; dynamic command builders that construct the pieces array manually; buggy middleware that injects null placeholders.

Understand the failure class

Related errors


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