{"record":{"id":"ac911e155488c01f","repo":"can1357/oh-my-pi","slug":"jina-api-returned-invalid-response-expected-an-ob","errorCode":null,"errorMessage":"Jina API returned invalid response: expected an object or array","messagePattern":"Jina API returned invalid response: expected an object or array","errorType":"validation","errorClass":"SearchProviderError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/web/search/providers/jina.ts","lineNumber":83,"sourceCode":"\tif (site) headers[\"X-Site\"] = site;\n\theaders[\"X-Respond-With\"] = \"no-content\";\n\theaders[\"X-Retain-Images\"] = \"none\";\n\tconst response = await fetchImpl(requestUrl, {\n\t\theaders,\n\t\tsignal: withHardTimeout(signal, timeoutMs),\n\t});\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text();\n\t\tconst classified = classifyProviderHttpError(\"jina\", response.status, errorText);\n\t\tif (classified) throw classified;\n\t\tthrow new SearchProviderError(\"jina\", `Jina API error (${response.status}): ${errorText}`, response.status);\n\t}\n\n\tconst payload = (await response.json()) as JinaSearchEnvelope | JinaSearchResponse | null;\n\tif (Array.isArray(payload)) return payload;\n\tif (!payload || typeof payload !== \"object\") {\n\t\tthrow new SearchProviderError(\"jina\", \"Jina API returned invalid response: expected an object or array\");\n\t}\n\tif (typeof payload.code === \"number\" && payload.code !== 200) {\n\t\tthrow new SearchProviderError(\"jina\", `Jina API response reported failure (${payload.code})`, payload.code);\n\t}\n\tif (!Array.isArray(payload.data)) {\n\t\tthrow new SearchProviderError(\"jina\", \"Jina API returned invalid response: expected data array\");\n\t}\n\treturn payload.data as JinaSearchResponse;\n}\n\n/** Execute Jina web search. */\nexport async function searchJina(params: JinaSearchParams): Promise<SearchResponse> {\n\tconst numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);\n\tconst keyOrResolver: ApiKey = params.authStorage.resolver(\"jina\", {\n\t\tsessionId: params.sessionId,\n\t});\n\tconst response = await withAuth(\n\t\tkeyOrResolver,","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/web/search/providers/jina.ts#L65-L101","documentation":"Thrown by callJinaSearch when a 2xx response body from the Jina search API is neither a JSON array of results nor a JSON object (e.g. null, a string, a number). The provider's contract is that Jina returns either a bare results array or an envelope object with code/data, so anything else is treated as an invalid upstream response with no status code.","triggerScenarios":"response.json() on a 200 response resolves to null, a string, or a number instead of an array or object — e.g. an empty body, an HTML/text interstitial mislabeled as JSON, or a middleman rewriting the payload.","commonSituations":"Proxy/CDN intercepting the request and returning an empty or HTML 200 response; captive portals; Jina API contract changes; custom fetch implementations returning stubbed bodies in tests.","solutions":["Retry the request; a 200-with-junk body is often transient middleware behavior.","Bypass proxies/interceptors so the raw Jina JSON reaches the client.","Verify network egress isn't a captive portal or hijacking DNS for s.jina.ai.","Update the package if Jina changed its response envelope; the parser is updated alongside their API."],"exampleFix":"// before: test stub returning junk\nconst fetchImpl = async () => new Response(null, { status: 200 });\n// after: envelope-shaped stub\nconst fetchImpl = async () => new Response(JSON.stringify({ code: 200, data: [] }), { status: 200 });","handlingStrategy":"validation","validationCode":null,"typeGuard":"function isJinaResultsPayload(p: unknown): p is { code?: number; data: unknown[] } | unknown[] {\n  return Array.isArray(p) || (typeof p === 'object' && p !== null && Array.isArray((p as { data?: unknown }).data));\n}","tryCatchPattern":"try {\n  return await search({ provider: 'jina', query });\n} catch (err) {\n  if (err instanceof SearchProviderError && err.message.includes('invalid response')) {\n    return await retryWithBackoff(() => search({ provider: 'jina', query }));\n  }\n  throw err;\n}","preventionTips":["Do not put rewriting proxies in front of s.jina.ai that can return non-JSON 200 bodies.","Detect captive portals / DNS hijacking in corporate networks.","Retry once on invalid-response errors; they are usually transient middleware artifacts.","Keep the client updated against Jina response-envelope changes."],"tags":["http","json","schema-validation-failed","jina","search-provider"],"backgroundTag":"invalid-json-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}