{"record":{"id":"31b8add7eee4e519","repo":"google/zx","slug":"callback-is-required-for-retry","errorCode":null,"errorMessage":"Callback is required for retry","messagePattern":"Callback is required for retry","errorType":"exception","errorClass":"Fail","httpStatus":null,"severity":"error","filePath":"src/goods.ts","lineNumber":217,"sourceCode":"  for await (const chunk of stream.setEncoding('utf8')) {\n    buf += chunk\n  }\n  return buf\n}\n\nexport async function retry<T>(count: number, callback: () => T): Promise<T>\nexport async function retry<T>(\n  count: number,\n  duration: Duration | Generator<number>,\n  callback: () => T\n): Promise<T>\nexport async function retry<T>(\n  count: number,\n  d: Duration | Generator<number> | (() => T),\n  cb?: () => T\n): Promise<T> {\n  if (typeof d === 'function') return retry(count, 0, d)\n  if (!cb) throw new Fail('Callback is required for retry')\n\n  const total = count\n  const gen =\n    typeof d === 'object'\n      ? d\n      : (function* (d) {\n          while (true) yield d\n        })(parseDuration(d))\n\n  let attempt = 0\n  let lastErr: unknown\n  while (count-- > 0) {\n    attempt++\n    try {\n      return await cb()\n    } catch (err) {\n      lastErr = err\n      const delay = gen.next().value","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/google/zx/blob/00a2c484e219c2e84bfc3a199febf7fbce2cfbf4/src/goods.ts#L199-L235","documentation":"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.","triggerScenarios":"`retry(3)`; `retry(3, '100ms')` (duration but no callback); passing an options object instead of a function; a refactor that removed the callback.","commonSituations":"Refactoring that dropped the callback; misreading the overload signature; passing a non-function placeholder.","solutions":["Pass the callback as the final argument: `retry(3, () => fetch(url))`.","With a delay, use the 3-arg form: `retry(3, '100ms', () => doThing())`.","Double-check overload order: (count, [duration | generator], callback)."],"exampleFix":"// before\nretry(3, '100ms')\n// after\nretry(3, '100ms', () => doThing())","handlingStrategy":"type-guard","validationCode":"function validateRetryArgs(count: number, d: unknown, cb?: unknown): void {\n  const fn = typeof d === 'function' ? d : cb\n  if (typeof fn !== 'function') {\n    throw new TypeError('retry: a callback function is required')\n  }\n}\n\nvalidateRetryArgs(3, '100ms', cb)","typeGuard":"const isCallback = (v: unknown): v is (...a: any[]) => any =>\n  typeof v === 'function'","tryCatchPattern":"import { Fail } from 'zx'\ntry {\n  await retry(3, maybeDuration)\n} catch (e) {\n  if (e instanceof Fail && /Callback is required for retry/.test(e.message)) {\n    throw new TypeError('retry(count, [duration], callback) — callback missing')\n  }\n  throw e\n}","preventionTips":["Always pass the callback as the final positional argument.","Remember the overload order: (count, [duration | generator], callback).","Use TypeScript overloads to catch missing callbacks at compile time."],"tags":["retry","api-misuse","callback"],"backgroundTag":null,"analyzedSha":"00a2c484e219c2e84bfc3a199febf7fbce2cfbf4","analyzedAt":"2026-08-13T02:11:06.305Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}