{"record":{"id":"90b220a3b400aba2","repo":"can1357/oh-my-pi","slug":"firecrawl-request-failed","errorCode":null,"errorMessage":"Firecrawl request failed","messagePattern":"Firecrawl request failed","errorType":"exception","errorClass":"SearchProviderError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/web/search/providers/firecrawl.ts","lineNumber":142,"sourceCode":"\t\theaders,\n\t\tbody: JSON.stringify(buildRequestBody(params)),\n\t\tsignal: withHardTimeout(params.signal, params.timeoutMs),\n\t});\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text();\n\t\tconst classified = classifyProviderHttpError(\"firecrawl\", response.status, errorText);\n\t\tif (classified) throw classified;\n\t\tthrow new SearchProviderError(\n\t\t\t\"firecrawl\",\n\t\t\t`Firecrawl API error (${response.status}): ${errorText}`,\n\t\t\tresponse.status,\n\t\t);\n\t}\n\n\tconst data = (await response.json()) as FirecrawlSearchResponse;\n\tif (data.success === false) {\n\t\tthrow new SearchProviderError(\"firecrawl\", data.error?.trim() || \"Firecrawl request failed\");\n\t}\n\treturn data;\n}\n\n/** ISO `YYYY-MM-DD` to Google `MM/DD/YYYY` for `tbs=cdr` custom date ranges. */\nfunction toGoogleDate(iso: string): string {\n\tconst [year, month, day] = iso.split(\"-\");\n\treturn `${month}/${day}/${year}`;\n}\n\n/**\n * Map explicit `before:`/`after:` bounds to a Firecrawl `tbs` custom date\n * range (`cdr:1,cd_min:MM/DD/YYYY,cd_max:MM/DD/YYYY`), or undefined when the\n * query carries no absolute date bounds.\n */\nfunction buildDateTbs(parsed: StructuredQuery): string | undefined {\n\tif (!parsed.after && !parsed.before) return undefined;\n\tconst parts = [\"cdr:1\"];","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/web/search/providers/firecrawl.ts#L124-L160","documentation":"After a successful HTTP response, callFirecrawlSearch() parses the JSON body; Firecrawl wraps results in a payload with a `success` boolean. When `success === false` and no more specific error was already raised, this generic fallback is thrown using the body's `error` field, or the literal 'Firecrawl request failed' if the error field is empty/missing.","triggerScenarios":"Firecrawl returned HTTP 200 but the JSON body has success:false — e.g. partial processing failures, the search completed at the HTTP layer but the provider-side job failed, or a proxy/gateway returned 200 with an error-shaped JSON.","commonSituations":"Firecrawl accepting the request but failing during search execution (upstream engine errors), a reverse proxy masking a backend failure with a 200 status, or a response whose error text is empty so only the fallback message appears.","solutions":["If the message contains provider detail from data.error, follow that specific guidance; if it is the bare fallback, enable request logging to capture the full response body","Retry the search — transient provider-side failures often succeed on a second attempt","Check Firecrawl status/dashboards for degraded service","Verify the API key and plan still permit search (some failures surface as success:false with HTTP 200)"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Validate the response envelope yourself before trusting it:\nfunction isSuccessfulFirecrawlPayload(data: unknown): data is { success: true; data: unknown[] } {\n  return typeof data === \"object\" && data !== null && (data as { success?: unknown }).success === true;\n}","typeGuard":"function isFirecrawlSuccess(data: FirecrawlSearchResponse): data is FirecrawlSearchResponse & { success: true } {\n  return data.success !== false;\n}","tryCatchPattern":"try {\n  results = await firecrawlSearch(query);\n} catch (err) {\n  if (err instanceof SearchProviderError && !err.status && err.message === \"Firecrawl request failed\") {\n    // HTTP 200 but provider-side failure — retry once, then surface\n    results = await firecrawlSearch(query).catch(() => fallbackSearch(query));\n  } else throw err;\n}","preventionTips":["Treat success:false as a retryable provider-side failure distinct from HTTP errors","Log the full response body on this error so the empty provider `error` field doesn't hide the cause","Add a single retry with delay before giving up","Watch provider status channels for degraded-search incidents"],"tags":["api-provider","response-parsing","network"],"backgroundTag":"api-request-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}