{"record":{"id":"060c65e60016ee19","repo":"crewAIInc/crewAI","slug":"brave-search-api-returned-invalid-json-http-resp","errorCode":null,"errorMessage":"Brave Search API returned invalid JSON (HTTP {resp.status_code}): {exc}","messagePattern":"Brave Search API returned invalid JSON \\(HTTP (.+?)\\): (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"lib/crewai-tools/src/crewai_tools/tools/brave_search_tool/base.py","lineNumber":218,"sourceCode":"            except requests.Timeout as exc:\r\n                raise RuntimeError(\r\n                    f\"Brave Search API request timed out after {self._timeout}s: {exc}\"\r\n                ) from exc\r\n\r\n            logger.debug(\r\n                \"Brave Search API request: %s %s -> %d\",\r\n                \"GET\",\r\n                resp.url,\r\n                resp.status_code,\r\n            )\r\n\r\n            # Response was OK, return the JSON body\r\n            if resp.ok:\r\n                try:\r\n                    result: dict[str, Any] = resp.json()\r\n                    return result\r\n                except ValueError as exc:\r\n                    raise RuntimeError(\r\n                        f\"Brave Search API returned invalid JSON (HTTP {resp.status_code}): {exc}\"\r\n                    ) from exc\r\n\r\n            # Response was not OK, but is retryable\r\n            # (e.g., 429 Too Many Requests, 500 Internal Server Error)\r\n            if _is_retryable(resp) and attempt < _max_retries - 1:\r\n                delay = _retry_delay(resp, attempt)\r\n                logger.warning(\r\n                    \"Brave Search API returned %d. Retrying in %.1fs (attempt %d/%d)\",\r\n                    resp.status_code,\r\n                    delay,\r\n                    attempt + 1,\r\n                    _max_retries,\r\n                )\r\n                time.sleep(delay)\r\n                last_resp = resp\r\n                continue\r\n\r","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/tools/brave_search_tool/base.py#L200-L236","documentation":"After a 200-series response, BraveSearchTool parses the body with resp.json(); if parsing raises ValueError (invalid or truncated JSON), it is converted to this RuntimeError including the HTTP status and parser error. This usually indicates a proxy or captive portal mangling the response, or a truncated body from a network fault — the request itself succeeded, so the tool does not retry.","triggerScenarios":"Corporate proxy / TLS-inspection appliance rewriting the response into HTML; captive portal or error page returned with 200; truncated chunked response on flaky links; a Brave CDN edge serving a corrupted body.","commonSituations":"Zscaler/Netskope-style TLS interception injecting block pages; hotel/airport captive portals; mobile networks truncating responses; rare CDN corruption incidents.","solutions":["Log resp-sized diagnostics: reproduce with `curl -s https://api.search.brave.com/... -H 'X-Subscription-Token: ...'` and inspect the raw body — HTML content points at a proxy/portal.","Exempt api.search.brave.com from TLS inspection or proxy rewriting, or route around the proxy.","Retry the call in your own code — truncated bodies are frequently transient.","If the raw body is genuinely malformed JSON from Brave with no intermediary, capture it and report to Brave support."],"exampleFix":"# before\nresults = tool._run(\"query\")\n\n# after\nfor attempt in range(3):\n    try:\n        results = tool._run(\"query\")\n        break\n    except RuntimeError as e:\n        if \"invalid JSON\" not in str(e) or attempt == 2:\n            raise","handlingStrategy":"retry","validationCode":"import requests\n\ndef returns_json(url: str, headers: dict) -> bool:\n    r = requests.get(url, headers=headers, timeout=10)\n    ct = r.headers.get(\"Content-Type\", \"\")\n    return \"json\" in ct.lower()","typeGuard":null,"tryCatchPattern":"except RuntimeError as e:\n    if \"invalid JSON\" in str(e):\n        result = tool._run(query)  # truncated bodies are often transient; retry once\n        if not isinstance(result, dict):\n            raise\n    else:\n        raise","preventionTips":["Exempt api.search.brave.com from TLS-inspecting proxies that rewrite bodies.","Validate the response Content-Type is application/json when calling HTTP APIs yourself.","Log resp text on JSON parse failures — an HTML block page from a proxy is the most common root cause."],"tags":["brave-search","json","parsing","proxy","network"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}