{"record":{"id":"a09a6726050cdb0e","repo":"ruvnet/RuView","slug":"non-json-response-from-method-path-status-re","errorCode":null,"errorMessage":"Non-JSON response from {method} {path} (status {resp.status}): {raw[:200]!r}","messagePattern":"Non-JSON response from (.+?) (.+?) \\(status (.+?)\\): (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scripts/seed_csi_bridge.py","lineNumber":240,"sourceCode":"                 timeout: int = 10, auth: bool = True) -> dict:\n        \"\"\"Issue an HTTP request and return parsed JSON.\n\n        Raises urllib.error.URLError on connection failure,\n        urllib.error.HTTPError on non-2xx status, and\n        ValueError on non-JSON response body.\n        \"\"\"\n        url = f\"{self.base_url}{path}\"\n        data = json.dumps(body).encode() if body is not None else None\n        headers = {\"Content-Type\": \"application/json\"}\n        if auth:\n            headers[\"Authorization\"] = f\"Bearer {self.token}\"\n        req = urllib.request.Request(url, data=data, headers=headers, method=method)\n        with urllib.request.urlopen(req, context=self.ctx, timeout=timeout) as resp:\n            raw = resp.read()\n            try:\n                return json.loads(raw)\n            except (json.JSONDecodeError, ValueError) as exc:\n                raise ValueError(\n                    f\"Non-JSON response from {method} {path} \"\n                    f\"(status {resp.status}): {raw[:200]!r}\"\n                ) from exc\n\n    def ingest(self, vectors: list[tuple[int, list[float]]]) -> dict:\n        \"\"\"Ingest vectors into the RVF store.\"\"\"\n        return self._request(\"POST\", \"/api/v1/store/ingest\", {\"vectors\": vectors})\n\n    def query(self, vector: list[float], k: int = 5) -> dict:\n        \"\"\"Query kNN for a vector.\"\"\"\n        return self._request(\"POST\", \"/api/v1/store/query\", {\"vector\": vector, \"k\": k})\n\n    def compact(self) -> dict:\n        \"\"\"Trigger store compaction.\"\"\"\n        return self._request(\"POST\", \"/api/v1/store/compact\")\n\n    def status(self) -> dict:\n        \"\"\"Get device status.\"\"\"","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/ruvnet/RuView/blob/4685618388a5e49fad5b3005806f3bdd6a7c25c3/scripts/seed_csi_bridge.py#L222-L258","documentation":"SeedClient._request() in scripts/seed_csi_bridge.py issues JSON-over-HTTPS calls (urllib, Bearer token, TLS verification disabled for the bridge's self-signed cert) to the Cognitum Seed REST API. When the server responds with a 2xx status but the body fails json.loads, it raises ValueError including the method, path, status, and the first 200 raw bytes. A non-JSON body almost always means the URL reached something other than the Seed API.","triggerScenarios":"POST /api/v1/store/ingest or /api/v1/store/query where base_url points at a wrong host/port serving HTML (router admin page, reverse-proxy error page, captive portal), the server returns an empty 200 body, or a redirect lands on a login page that answers 200 with HTML.","commonSituations":"Wrong port in the base_url; an nginx/ingress in front that returns a styled 200/3xx page for unknown routes; the API deployed at a subpath while the client assumes root; token flow changed so the server returns a plain-text response instead of JSON.","solutions":["Reproduce with curl and inspect the body: `curl -k -H 'Authorization: Bearer <token>' -d '{\"k\":1}' <base_url>/api/v1/store/query` — the first 200 bytes in the error already tell you what answered","Fix base_url to the exact host:port of the Seed bridge (no trailing slash needed; the client rstrips it)","If a reverse proxy fronts the bridge, add routing so /api/v1/* reaches it and errors stay JSON","Confirm the deployment actually serves the /api/v1/store/* routes at that URL"],"exampleFix":"# before\nclient = SeedClient(\"https://10.0.0.1\", token)  # 10.0.0.1 is the router admin UI -> HTML 200\n\n# after\nclient = SeedClient(\"https://10.0.0.1:8443\", token)  # actual Seed bridge port","handlingStrategy":"try-catch","validationCode":"# Probe that the endpoint speaks JSON before bulk ingesting:\nresp = client._request(\"POST\", \"/api/v1/store/query\", {\"vector\": [0.0] * 128, \"k\": 1})  # one cheap call first","typeGuard":"def is_seed_api_response(payload: bytes) -> bool:\n    import json\n    try:\n        json.loads(payload)\n        return True\n    except (json.JSONDecodeError, ValueError):\n        return False","tryCatchPattern":"try:\n    result = client.ingest(vectors)\nexcept ValueError as e:\n    # e message embeds status + first 200 raw bytes: read it to identify HTML proxy pages\n    raise SystemExit(f\"Seed bridge not speaking JSON — check base_url/port: {e}\") from e\nexcept urllib.error.URLError as e:\n    raise SystemExit(f\"Seed bridge unreachable: {e}\") from e","preventionTips":["Verify base_url host:port with a single curl -k query before starting a bulk ingest","The client disables TLS verification for self-signed certs — make sure you are not silently hitting a different HTTPS host","Keep the /api/v1 prefix intact and confirm the deployment serves JSON on error paths too"],"tags":["http","json","api-client","ssl","urllib","python","scripts","network"],"backgroundTag":null,"analyzedSha":"4685618388a5e49fad5b3005806f3bdd6a7c25c3","analyzedAt":"2026-08-16T06:09:40.886Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}