{"record":{"id":"3505aa463c821127","repo":"can1357/oh-my-pi","slug":"perplexity-ask-api-error-response-status-e","errorCode":null,"errorMessage":"Perplexity ask API error (${response.status}): ${errorText}","messagePattern":"Perplexity ask API error \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"SearchProviderError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/web/search/providers/perplexity.ts","lineNumber":729,"sourceCode":"\t};\n\n\t// The consumer ask endpoint intermittently drops the socket before sending an\n\t// HTTP response (#5315). Retry the transport exactly once; once we hold an\n\t// HTTP response (handled below) the outcome — including non-2xx — is final and\n\t// never retried, so a real 401/429 is never papered over by a second attempt.\n\tlet response: Response;\n\ttry {\n\t\tresponse = await (params.fetch ?? fetch)(PERPLEXITY_OAUTH_ASK_URL, requestInit);\n\t} catch (error) {\n\t\tif (params.signal?.aborted) throw error;\n\t\tresponse = await (params.fetch ?? fetch)(PERPLEXITY_OAUTH_ASK_URL, requestInit);\n\t}\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text();\n\t\tconst classified = classifyProviderHttpError(\"perplexity\", response.status, errorText);\n\t\tif (classified) throw classified;\n\t\tthrow new SearchProviderError(\n\t\t\t\"perplexity\",\n\t\t\t`Perplexity ask API error (${response.status}): ${errorText}`,\n\t\t\tresponse.status,\n\t\t);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new SearchProviderError(\"perplexity\", \"Perplexity ask API returned no response body\", 500);\n\t}\n\n\tlet answer = \"\";\n\tlet model: string | undefined;\n\tlet finalRequestId: string | undefined;\n\tconst sourcesByUrl = new Map<string, SearchSource>();\n\tlet mergedEvent: PerplexityOAuthStreamEvent = { blocks: [] };\n\n\tfor await (const event of readSseJson<PerplexityOAuthStreamEvent>(response.body, params.signal)) {\n\t\tif (event.error_code) {","sourceCodeStart":711,"sourceCodeEnd":747,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/web/search/providers/perplexity.ts#L711-L747","documentation":"The Perplexity ask (OAuth/cookie) endpoint returned a non-ok HTTP response. The body text is captured, offered to classifyProviderHttpError, and otherwise thrown as a SearchProviderError including the response status and the raw error body.","triggerScenarios":"callPerplexityAsk's fetch of the ask API returns response.ok === false — e.g. expired OAuth access token (401), CSRF/cookie failure (403), rate limiting (429), or Perplexity server errors (5xx).","commonSituations":"Stale or revoked Perplexity OAuth session, anonymous-mode IP blocked/challenged by Perplexity, per-account rate limits, Perplexity outages.","solutions":["Inspect response.status embedded in the message; 401/403 -> re-authenticate (refresh OAuth token or provide cookies)","If 429, back off and retry later","If 5xx, retry after a delay or check Perplexity status","Fall back to a different search provider for this query"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// ensure an auth path exists before calling\nconst hasAuth = Boolean(process.env.PERPLEXITY_API_KEY) || hasStoredOAuthSession();","typeGuard":"function isSearchProviderError(e: unknown): e is SearchProviderError {\n  return e instanceof SearchProviderError;\n}","tryCatchPattern":"try {\n  return await perplexityAsk(params);\n} catch (err) {\n  if (err instanceof SearchProviderError && /status (401|403)/.test(err.message)) {\n    await refreshOAuthSession(); // re-authenticate once, then retry\n  }\n  throw err;\n}","preventionTips":["Refresh OAuth tokens proactively before expiry","Back off on 429 instead of hammering the endpoint","Monitor Perplexity status for 5xx windows"],"tags":["network","http","web-search","perplexity","auth"],"backgroundTag":"search-provider-http-error","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}