{"record":{"id":"c8302611b3555186","repo":"tursodatabase/turso","slug":"retryfetch-attempts-must-be-a-finite-integer-1","errorCode":null,"errorMessage":"retryFetch: attempts must be a finite integer >= 1, got ${attempts}","messagePattern":"retryFetch: attempts must be a finite integer >= 1, got (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"bindings/javascript/sync/packages/common/run.ts","lineNumber":159,"sourceCode":" * ```ts\n * import { connect } from '@tursodatabase/sync';\n * import { retryFetch } from '@tursodatabase/sync-common';\n *\n * const db = await connect({\n *   path: 'local.db',\n *   url: 'libsql://...',\n *   fetch: retryFetch(),                              // defaults\n *   // fetch: retryFetch({ attempts: 5, delayMs: 1000 }),\n * });\n * ```\n */\nexport function retryFetch(opts: RetryFetchOpts = {}): typeof fetch {\n    const attempts = opts.attempts ?? 3;\n    const baseDelay = opts.delayMs ?? 500;\n    const backoff = opts.backoff ?? 2;\n    const underlying: typeof fetch = opts.fetch ?? ((input, init) => fetch(input, init));\n    if (!Number.isFinite(attempts) || attempts < 1) {\n        throw new Error(`retryFetch: attempts must be a finite integer >= 1, got ${attempts}`);\n    }\n    return async (input: RequestInfo | URL, init?: RequestInit) => {\n        let lastError: unknown = null;\n        let lastResponse: Response | null = null;\n        let delay = baseDelay;\n        for (let i = 0; i < attempts; i++) {\n            try {\n                const response = await underlying(input, init);\n                if (response.status < 500 && response.status !== 429) {\n                    return response;\n                }\n                lastResponse = response;\n                lastError = null;\n            } catch (error) {\n                lastError = error;\n                lastResponse = null;\n            }\n            if (i + 1 < attempts) {","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/bindings/javascript/sync/packages/common/run.ts#L141-L177","documentation":"retryFetch() validates its options eagerly when the wrapped fetch function is created, and throws this message when attempts is not a finite number or is below 1. It is a configuration-time error: nothing has been fetched yet. Note the message says integer while the check is Number.isFinite(attempts) && attempts >= 1, so non-integer values pass — the failure is specifically NaN, +/-Infinity, 0, or negatives.","triggerScenarios":"retryFetch({ attempts: 0 }), { attempts: -1 }, or { attempts: NaN }; a retries value parsed from an environment variable that is empty or non-numeric (Number('') === 0, Number('abc') === NaN); a config default of 0 copied from another tool; Infinity from parseInt of a huge string.","commonSituations":"RETRY_ATTEMPTS env var unset in one environment so the derived value becomes NaN/0; config schemas that allow 0 to mean 'no retries'; spreads of partial option objects where attempts is computed as undefined - 1.","solutions":["Pass an integer >= 1, e.g. retryFetch({ attempts: 3 }).","Sanitize config-derived values: const attempts = Number.isFinite(n) && n >= 1 ? Math.floor(n) : 3.","Use 1 (single attempt, no retries) rather than 0 to disable retrying.","Check for typos in the option name — attempt instead of attempts silently falls back to the default 3, so a 0/NaN elsewhere is usually the culprit."],"exampleFix":"// before\nconst attempts = Number(process.env.SYNC_RETRIES); // NaN or 0 when unset/misformatted\nconst fetchWithRetry = retryFetch({ attempts }); // throws\n\n// after\nconst raw = Number(process.env.SYNC_RETRIES);\nconst attempts = Number.isFinite(raw) && raw >= 1 ? Math.floor(raw) : 3;\nconst fetchWithRetry = retryFetch({ attempts });","handlingStrategy":"validation","validationCode":"const raw = Number(config.retries);\nconst attempts = Number.isFinite(raw) && raw >= 1 ? Math.floor(raw) : 3;\nconst fetchWithRetry = retryFetch({ attempts });","typeGuard":"const isValidRetryAttempts = (n: unknown): n is number =>\n  typeof n === 'number' && Number.isFinite(n) && n >= 1;","tryCatchPattern":null,"preventionTips":["Validate env-derived retry counts before passing them to retryFetch().","Use 1, not 0, to disable retries.","Floor fractional values so config like 2.5 does not surprise you later."],"tags":["retry","fetch","validation","configuration","javascript"],"backgroundTag":"invalid-option-value","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}