google/zx · error · Fail

Callback is required for retry

Error message

Callback is required for retry

What it means

Thrown by retry() when no callback function is provided. With the 2-arg overload retry(count, cb), the 2nd argument must be a function; with the 3-arg overload retry(count, duration, cb), the 3rd must be a function. If the final positional argument is not a function (e.g. only count and a duration were passed, or the callback was dropped), retry refuses to proceed.

Source

Thrown at src/goods.ts:217

  for await (const chunk of stream.setEncoding('utf8')) {
    buf += chunk
  }
  return buf
}

export async function retry<T>(count: number, callback: () => T): Promise<T>
export async function retry<T>(
  count: number,
  duration: Duration | Generator<number>,
  callback: () => T
): Promise<T>
export async function retry<T>(
  count: number,
  d: Duration | Generator<number> | (() => T),
  cb?: () => T
): Promise<T> {
  if (typeof d === 'function') return retry(count, 0, d)
  if (!cb) throw new Fail('Callback is required for retry')

  const total = count
  const gen =
    typeof d === 'object'
      ? d
      : (function* (d) {
          while (true) yield d
        })(parseDuration(d))

  let attempt = 0
  let lastErr: unknown
  while (count-- > 0) {
    attempt++
    try {
      return await cb()
    } catch (err) {
      lastErr = err
      const delay = gen.next().value

View on GitHub (pinned to 00a2c484e2)

Solutions

  1. Pass the callback as the final argument: `retry(3, () => fetch(url))`.
  2. With a delay, use the 3-arg form: `retry(3, '100ms', () => doThing())`.
  3. Double-check overload order: (count, [duration | generator], callback).

Example fix

// before
retry(3, '100ms')
// after
retry(3, '100ms', () => doThing())
Defensive patterns

Strategy: type-guard

Validate before calling

function validateRetryArgs(count: number, d: unknown, cb?: unknown): void {
  const fn = typeof d === 'function' ? d : cb
  if (typeof fn !== 'function') {
    throw new TypeError('retry: a callback function is required')
  }
}

validateRetryArgs(3, '100ms', cb)

Type guard

const isCallback = (v: unknown): v is (...a: any[]) => any =>
  typeof v === 'function'

Try / catch

import { Fail } from 'zx'
try {
  await retry(3, maybeDuration)
} catch (e) {
  if (e instanceof Fail && /Callback is required for retry/.test(e.message)) {
    throw new TypeError('retry(count, [duration], callback) — callback missing')
  }
  throw e
}

Prevention

When it happens

Trigger: `retry(3)`; `retry(3, '100ms')` (duration but no callback); passing an options object instead of a function; a refactor that removed the callback.

Common situations: Refactoring that dropped the callback; misreading the overload signature; passing a non-function placeholder.

Related errors


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