cube-js/cube · error · Error
Option times in asyncRetry, must be a positive integer
Error message
Option times in asyncRetry, must be a positive integer
What it means
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.
Source
Thrown at packages/cubejs-backend-shared/src/promises.ts:459
call.release = refreshInterval.cancel;
return call;
};
export type RetryOptions = {
times: number,
};
/**
* High order function that do retry when async function throw an exception
*/
export const asyncRetry = async <Ret>(
fn: () => Promise<Ret>,
options: RetryOptions
) => {
if (options.times <= 0) {
throw new Error('Option times in asyncRetry, must be a positive integer');
}
let latestException: unknown = null;
for (let i = 0; i < options.times; i++) {
try {
return await fn();
} catch (e) {
latestException = e;
}
}
throw latestException;
};
View on GitHub (pinned to 7d981676b3)
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;`).
Example fix
// before
await asyncRetry(fn, { times: process.env.RETRY_TIMES as any });
// after
const times = parseInt(process.env.RETRY_TIMES ?? '3', 10);
await asyncRetry(fn, { times: Number.isInteger(times) && times > 0 ? times : 3 }); Defensive patterns
Strategy: validation
Validate before calling
function assertTimes(times: unknown): asserts times is number {
if (!Number.isInteger(times) || (times as number) <= 0) {
throw new Error('Option times in asyncRetry, must be a positive integer');
}
} Type guard
const isValidTimes = (t: unknown): t is number => typeof t === 'number' && Number.isInteger(t) && t > 0;
Try / catch
try {
return await asyncRetry(fn, options);
} catch (e) {
if (e instanceof Error && e.message.includes('must be a positive integer')) {
return await asyncRetry(fn, { ...options, times: 3 });
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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).
Related errors
- A user-defined contextToApiScopes function returns a wrong s
- Value "${input}" is not valid for ${envName}. ${description}
- Value "${input}" is not valid for ${envName}. Should be a po
- Value "${input}" is not valid for ${envName}. Should be lowe
- Value "${value}" is not valid for CUBEJS_MAX_REQUEST_SIZE. M
AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02).
Data as JSON: /api/errors/b5fadc9c7f582061.
Report an issue: GitHub.