google/zx · error · Error

Invalid duration: "${d}".

Error message

Invalid duration: "${d}".

What it means

Thrown by parseDuration() when the input is a number that is NaN or negative. Durations must be a non-negative finite number, or a string matching /^(\d+)(m?s?)$/ (e.g. '100ms', '2s', '3m'). parseDuration backs sleep(), retry() delays, expBackoff(), and $.timeout, so any of those surfaces can surface this error. Note: this is thrown as a plain `new Error`, not a zx Fail instance.

Source

Thrown at src/util.ts:106

      .replace(/\v/g, '\\v')
      .replace(/\0/g, '\\0') +
    `'`
  )
}

export function quotePowerShell(arg: string): string {
  if (arg === '') return `''`
  if (/^[\w/.\-@:=,+%]+$/.test(arg)) return arg

  return `'` + arg.replace(/'/g, "''") + `'`
}

export type Duration =
  number | `${number}` | `${number}m` | `${number}s` | `${number}ms`

export function parseDuration(d: Duration) {
  if (typeof d === 'number') {
    if (isNaN(d) || d < 0) throw new Error(`Invalid duration: "${d}".`)
    return d
  }
  const [m, v, u] = d.match(/^(\d+)(m?s?)$/) || []
  if (!m) throw new Error(`Unknown duration: "${d}".`)

  return +v * ({ s: 1000, ms: 1, m: 60_000 }[u] || 1)
}

export const once = <T extends (...args: any[]) => any>(fn: T) => {
  let called = false
  let result: ReturnType<T>

  return (...args: Parameters<T>): ReturnType<T> =>
    called ? result : ((called = true), (result = fn(...args)))
}

export const proxyOverride = <T extends object>(
  origin: T,

View on GitHub (pinned to 00a2c484e2)

Solutions

  1. Validate before passing: `if (Number.isFinite(d) && d >= 0) ...`.
  2. Clamp to a safe minimum: `Math.max(0, d)`.
  3. Prefer the explicit string form ('100ms', '2s') for readability and to avoid NaN.
  4. Guard config-driven values and treat -1/NaN as 'no timeout'.

Example fix

// before
$.timeout = rawTimeout   // rawTimeout may be -1 or NaN
// after
$.timeout = Number.isFinite(rawTimeout) && rawTimeout >= 0 ? rawTimeout : undefined
Defensive patterns

Strategy: validation

Validate before calling

import { parseDuration } from 'zx'

function safeDuration(d: number | string | undefined): number | undefined {
  if (d == null) return undefined
  const n = typeof d === 'number' ? d : Number(d)
  if (!Number.isFinite(n) || n < 0) return undefined // treat as 'no duration'
  return parseDuration(d as any)
}

const delay = safeDuration(raw)

Type guard

const isValidDurationNumber = (v: unknown): v is number =>
  typeof v === 'number' && Number.isFinite(v) && v >= 0

Try / catch

try {
  await sleep(duration)
} catch (e) {
  if (e instanceof Error && /Invalid duration/.test(e.message)) {
    // skip the wait; duration was NaN/negative
    return
  }
  throw e
}

Prevention

When it happens

Trigger: `sleep(-1)`; `$.timeout = -100`; `retry(3, NaN, fn)`; `parseDuration(Number('abc'))` (NaN); `expBackoff(-5)`; a computed duration that underflows to negative.

Common situations: Subtracting timestamps that yield a negative delta; parseFloat on malformed input producing NaN; config that uses -1 to mean 'disabled' fed directly into zx; overflow/underflow in delay math.

Related errors


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