{"record":{"id":"b5fadc9c7f582061","repo":"cube-js/cube","slug":"option-times-in-asyncretry-must-be-a-positive-int","errorCode":null,"errorMessage":"Option times in asyncRetry, must be a positive integer","messagePattern":"Option times in asyncRetry, must be a positive integer","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cubejs-backend-shared/src/promises.ts","lineNumber":459,"sourceCode":"\n  call.release = refreshInterval.cancel;\n\n  return call;\n};\n\nexport type RetryOptions = {\n  times: number,\n};\n\n/**\n * High order function that do retry when async function throw an exception\n */\nexport const asyncRetry = async <Ret>(\n  fn: () => Promise<Ret>,\n  options: RetryOptions\n) => {\n  if (options.times <= 0) {\n    throw new Error('Option times in asyncRetry, must be a positive integer');\n  }\n\n  let latestException: unknown = null;\n\n  for (let i = 0; i < options.times; i++) {\n    try {\n      return await fn();\n    } catch (e) {\n      latestException = e;\n    }\n  }\n\n  throw latestException;\n};\n","sourceCodeStart":441,"sourceCodeEnd":474,"githubUrl":"https://github.com/cube-js/cube/blob/7d981676b36392fec34088b9afab6bdcad40207c/packages/cubejs-backend-shared/src/promises.ts#L441-L474","documentation":"asyncRetry() validates its options up front: options.times must be a positive integer (> 0). Passing 0, a negative number, or a non-numeric value throws this error immediately instead of attempting any retries. It is a fail-fast contract error so misconfiguration is caught before fn is ever invoked.","triggerScenarios":"Calling asyncRetry(fn, { times: 0 }), { times: -1 }, or { times: undefined as any } — e.g. computing times from config/environment where a default is missing or parsed incorrectly (parseInt of a bad string yielding NaN).","commonSituations":"RETRY_TIMES env var unset and parsed to NaN; config default of 0 intended to mean 'disabled' rather than 'no attempts'; YAML/JSON config typo (times: null).","solutions":["Pass a positive integer for options.times (e.g. 3 or 5).","Coerce and validate config before calling: Number.isInteger(times) && times > 0, else fall back to a sane default.","If 'no retries' is desired, call fn directly instead of asyncRetry (times must still be >= 1).","Fix the env/config parsing that produced the invalid value (e.g. `const times = parseInt(v, 10) || 3;`)."],"exampleFix":"// before\nawait asyncRetry(fn, { times: process.env.RETRY_TIMES as any });\n// after\nconst times = parseInt(process.env.RETRY_TIMES ?? '3', 10);\nawait asyncRetry(fn, { times: Number.isInteger(times) && times > 0 ? times : 3 });","handlingStrategy":"validation","validationCode":"function assertTimes(times: unknown): asserts times is number {\n  if (!Number.isInteger(times) || (times as number) <= 0) {\n    throw new Error('Option times in asyncRetry, must be a positive integer');\n  }\n}","typeGuard":"const isValidTimes = (t: unknown): t is number =>\n  typeof t === 'number' && Number.isInteger(t) && t > 0;","tryCatchPattern":"try {\n  return await asyncRetry(fn, options);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('must be a positive integer')) {\n    return await asyncRetry(fn, { ...options, times: 3 });\n  }\n  throw e;\n}","preventionTips":["Always pass a literal positive integer for times, or validate config before the call.","Sanitize env-derived values: parseInt with a fallback default (e.g. `|| 3`).","Remember times >= 1 (one attempt minimum); 'disabled' means don't call asyncRetry.","Add a unit test asserting retry options parsing for every env-backed knob."],"tags":["validation","retry","configuration"],"backgroundTag":"invalid-retry-options","analyzedSha":"7d981676b36392fec34088b9afab6bdcad40207c","analyzedAt":"2026-09-02T03:45:10.400Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}