{"record":{"id":"cba7b74c31eb5298","repo":"koala73/worldmonitor","slug":"acquisition-provider-name-is-not-configured","errorCode":null,"errorMessage":"Acquisition provider '${name}' is not configured. Set the required API key env var.","messagePattern":"Acquisition provider '(.+?)' is not configured\\. Set the required API key env var\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"consumer-prices-core/src/acquisition/registry.ts","lineNumber":25,"sourceCode":"const _providers = new Map<AcquisitionProviderName, AcquisitionProvider>();\n\nexport function initProviders(env: Record<string, string | undefined>) {\n  _providers.set('playwright', new PlaywrightProvider());\n\n  if (env.EXA_API_KEY) {\n    _providers.set('exa', new ExaProvider(env.EXA_API_KEY));\n  }\n  if (env.FIRECRAWL_API_KEY) {\n    _providers.set('firecrawl', new FirecrawlProvider(env.FIRECRAWL_API_KEY));\n  }\n  if (env.P0_API_KEY) {\n    _providers.set('p0', new P0Provider(env.P0_API_KEY, env.P0_BASE_URL));\n  }\n}\n\nexport function getProvider(name: AcquisitionProviderName): AcquisitionProvider {\n  const p = _providers.get(name);\n  if (!p) throw new Error(`Acquisition provider '${name}' is not configured. Set the required API key env var.`);\n  return p;\n}\n\nexport async function teardownAll(): Promise<void> {\n  for (const p of _providers.values()) {\n    await p.teardown?.();\n  }\n  _providers.clear();\n}\n\n/**\n * Fetch a URL using the provider chain defined in config.\n * Tries primary provider first; on failure, tries fallback.\n */\nexport async function fetchWithFallback(\n  url: string,\n  config: AcquisitionConfig,\n  opts?: FetchOptions,","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/consumer-prices-core/src/acquisition/registry.ts#L7-L43","documentation":"registry.getProvider() throws when the requested name is absent from the provider map. initProviders() registers 'playwright' unconditionally, but 'exa', 'firecrawl' and 'p0' only when EXA_API_KEY, FIRECRAWL_API_KEY or P0_API_KEY were present at init time. So this error means one of: initProviders never ran, it ran without the key, or teardownAll() cleared the map mid-process.","triggerScenarios":"getProvider('firecrawl') when FIRECRAWL_API_KEY was unset when initProviders(env) ran; calling getProvider before initProviders; a second scrape phase calling getProvider after teardownAll() cleared the registry; env loaded from a different source than the object passed to initProviders.","commonSituations":"CI or a fresh deployment missing the secret; .env loaded after initProviders(process.env) already captured an empty env; a long-lived worker that tears down after one run then handles another request; a typo'd env var name in the deploy manifest.","solutions":["Set the missing env var (EXA_API_KEY / FIRECRAWL_API_KEY / P0_API_KEY) in the environment initProviders reads","Ensure initProviders(process.env) runs before any getProvider call","If this fires after teardownAll(), re-run initProviders for the next batch instead of reusing the cleared registry","Pass the same env object to initProviders that the config loader validated"],"exampleFix":"// before — registry consulted before env was loaded\nconst p = getProvider('firecrawl');\n\n// after — init once, after env is loaded\ninitProviders(process.env);\nconst p = getProvider('firecrawl'); // throws only if FIRECRAWL_API_KEY is truly absent","handlingStrategy":"validation","validationCode":"const KEY_FOR: Partial<Record<AcquisitionProviderName, string>> = {\n  exa: 'EXA_API_KEY', firecrawl: 'FIRECRAWL_API_KEY', p0: 'P0_API_KEY',\n};\n// fail fast at boot: every provider named by any retailer config must have its key\nfor (const cfg of retailerConfigs) {\n  for (const name of [cfg.acquisition?.provider, cfg.acquisition?.fallback]) {\n    if (name && KEY_FOR[name] && !process.env[KEY_FOR[name]]) {\n      throw new Error(`missing ${KEY_FOR[name]} for provider '${name}'`);\n    }\n  }\n}","typeGuard":"function providerAvailable(name: AcquisitionProviderName): boolean {\n  try { getProvider(name); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  return getProvider(name);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('is not configured')) {\n    return getProvider('playwright'); // always registered — degrade instead of crash\n  }\n  throw err;\n}","preventionTips":["Validate provider env vars against retailer acquisition configs at startup, not lazily mid-scrape","Call initProviders once after env loading, and never after teardownAll in the same process","Name env vars in deploy manifests exactly: EXA_API_KEY, FIRECRAWL_API_KEY, P0_API_KEY"],"tags":["registry","missing-env-var","configuration","startup"],"backgroundTag":"missing-env-var","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}