{"record":{"id":"1d32942c9a568820","repo":"tinyhumansai/openhuman","slug":"failed-to-read-exa-response-json-e","errorCode":null,"errorMessage":"Failed to read Exa response JSON: {e}","messagePattern":"Failed to read Exa response JSON: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/search/tools/exa.rs","lineNumber":248,"sourceCode":"        if !status.is_success() {\n            // Read and drop the body: Exa echoes the query back in errors and\n            // the message reaches the agent transcript.\n            let body_len = resp.text().await.unwrap_or_default().len();\n            tracing::warn!(status = %status, body_len, \"[exa] non-2xx response\");\n            if status == reqwest::StatusCode::UNAUTHORIZED\n                || status == reqwest::StatusCode::FORBIDDEN\n            {\n                anyhow::bail!(\n                    \"Exa rejected the configured API key (HTTP {status}). \\\n                     Check your Exa API key under Connections > Search engine.\"\n                );\n            }\n            anyhow::bail!(\"Exa returned non-2xx status {status}\");\n        }\n\n        resp.json().await.map_err(|e| {\n            tracing::warn!(\"[exa] failed to read response JSON: {e}\");\n            anyhow::anyhow!(\"Failed to read Exa response JSON: {e}\")\n        })\n    }\n\n    async fn post_documents(&self, path: &str, body: Value) -> anyhow::Result<Vec<ExaResultItem>> {\n        let value = self.post(path, body).await?;\n        let parsed: ExaSearchResponse = serde_json::from_value(value).map_err(|e| {\n            tracing::warn!(\"[exa] failed to parse {path} response: {e}\");\n            anyhow::anyhow!(\"Failed to parse Exa response: {e}\")\n        })?;\n        tracing::debug!(path, result_count = parsed.results.len(), \"[exa] call ok\");\n        Ok(parsed.results)\n    }\n\n    fn render_plain(&self, results: &[ExaResultItem], heading: &str, limit: usize) -> String {\n        if results.is_empty() {\n            return format!(\"No Exa results for: {heading}\");\n        }\n","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/search/tools/exa.rs#L230-L266","documentation":"Raised in ExaClient::post when reading the successful response body as JSON fails (resp.json() errors) — i.e. Exa answered with a non-JSON or truncated body. Note the two-step failure handling: non-2xx statuses are bailed earlier with a status-specific message (401/403 name the API key and deliberately do not echo Exa's error body into the transcript, since it echoes the query back), so this error means an HTTP-successful but unparseable response.","triggerScenarios":"Thrown at src/openhuman/search/tools/exa.rs:248 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Retry the request — a truncated or HTML error page (e.g. from an intercepting proxy) is often transient.","Check whether a proxy or captive portal is replacing the JSON body with HTML.","Verify the configured api_url points at the Exa API and not an intermediate gateway.","If persistent, capture body length in logs (already warned) and report to Exa support."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}