{"record":{"id":"977ed4d4783f411e","repo":"abhigyanpatwari/GitNexus","slug":"withretry-maxattempts-must-be-1-got-opts-ma","errorCode":null,"errorMessage":"withRetry: maxAttempts must be >= 1, got ${opts.maxAttempts}","messagePattern":"withRetry: maxAttempts must be >= 1, got (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"gitnexus-shared/src/integrations/retry.ts","lineNumber":79,"sourceCode":"  }\n  const exponential = baseDelayMs * Math.pow(2, attempt);\n  const upper = Math.min(capDelayMs, exponential);\n  return Math.floor(random() * upper);\n}\n\n/**\n * Execute `fn` with bounded retries.\n *\n * The classification of \"retryable\" is the caller's responsibility — see\n * `resilient-fetch.ts` for the GitHub-dispatch-specific rules. This\n * helper is the mechanical retry loop only.\n */\nexport async function withRetry<T>(\n  fn: (attempt: number) => Promise<T>,\n  opts: RetryOptions,\n): Promise<T> {\n  if (opts.maxAttempts < 1) {\n    throw new Error(`withRetry: maxAttempts must be >= 1, got ${opts.maxAttempts}`);\n  }\n  const sleep = opts.sleep ?? defaultSleep;\n  const random = opts.random ?? Math.random;\n\n  let lastError: unknown;\n  for (let attempt = 0; attempt < opts.maxAttempts; attempt++) {\n    try {\n      return await fn(attempt);\n    } catch (err) {\n      lastError = err;\n      const decision = opts.isRetryable(err, attempt);\n      if (!decision.retry) throw err;\n      // Don't sleep after the final attempt.\n      if (attempt + 1 >= opts.maxAttempts) break;\n      const delayMs = computeBackoffMs(\n        attempt,\n        opts.baseDelayMs,\n        opts.capDelayMs,","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus-shared/src/integrations/retry.ts#L61-L97","documentation":"An optional LadybugDB (DuckDB-lineage) extension failed to load, so GitNexus continues without that extension's features. The degradation is deduped per `${name}:${reason}` key so it warns once, and a quiet probe deliberately skips the dedup key so the owning caller reports the real degradation later. The message embeds a diagnosis from diagnoseExtensionLoad explaining why loading failed.","triggerScenarios":"ExtensionManager.load() fails for an optional extension — binary not installed for the platform/arch, LadybugDB version incompatible with the extension ABI, offline/filtered download of the extension bundle, or a corrupted extension file in the extensions directory.","commonSituations":"Upgrading LadybugDB without reinstalling matching extensions; air-gapped CI where the extension never got fetched; mixed-version installs where an extension built for an older ABI is present; musl-vs-glibc Linux mismatches.","solutions":["Reinstall or refresh the extension bundle so it matches the installed LadybugDB version","Check the `reason` / diagnosis in the message to identify the exact load failure (missing file vs incompatible version vs load error)","Verify platform/arch support for the extension on the current runtime","If the feature is unneeded, ignore the warning — core operation continues without it"],"exampleFix":"// after init: gate features on actual capabilities\nimport { getExtensionCapabilities } from 'gitnexus/dist/core/lbug/extension-loader.js';\nconst caps = getExtensionCapabilities();\nconst hasJson = caps.some((c) => c.name === 'json' && c.loaded);\nif (hasJson) { /* use json-extension-backed path */ } else { /* generic fallback path */ }","handlingStrategy":"fallback","validationCode":"import { getExtensionCapabilities } from 'gitnexus/dist/core/lbug/extension-loader.js';\n// after init: confirm the features you depend on actually loaded\nconst caps = getExtensionCapabilities();\nconst loaded = new Set(caps.filter((c) => c.loaded).map((c) => c.name));\nif (!loaded.has('json')) {\n  // take the non-extension code path instead of assuming the feature exists\n}","typeGuard":"type ExtensionCapability = { name: string; loaded: boolean };\nconst isCapabilityAvailable = (caps: ExtensionCapability[], name: string): boolean =>\n  caps.some((c) => c.name === name && c.loaded);","tryCatchPattern":null,"preventionTips":["Pin the LadybugDB and extension versions together when upgrading","Gate extension-dependent features on getExtensionCapabilities(), never on assumptions","On air-gapped CI, pre-install the extension bundle and verify it loads in a smoke step"],"tags":["ladybugdb","extension","optional-dependency"],"backgroundTag":"optional-extension-load-failed","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-31T04:17:50.494Z"}