{"record":{"id":"0565ba8e19e66bf5","repo":"can1357/oh-my-pi","slug":"bridge-call-name-r-non-json-response-body-20","errorCode":null,"errorMessage":"bridge call {name!r}: non-JSON response: {body[:200]!r}","messagePattern":"bridge call (.+?): non-JSON response: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/eval/py/prelude.py","lineNumber":414,"sourceCode":"        ).encode(\"utf-8\")\n        req = urllib.request.Request(\n            f\"{base}/v1/tool\",\n            data=payload,\n            method=\"POST\",\n            headers={\n                \"Content-Type\": \"application/json\",\n                \"Authorization\": f\"Bearer {token}\",\n            },\n        )\n        try:\n            with _BRIDGE_OPENER.open(req) as resp:\n                body = resp.read()\n        except urllib.error.HTTPError as exc:\n            body = exc.read()\n        try:\n            data = json.loads(body)\n        except json.JSONDecodeError:\n            raise RuntimeError(\n                f\"bridge call {name!r}: non-JSON response: {body[:200]!r}\"\n            ) from None\n        if not isinstance(data, dict) or not data.get(\"ok\"):\n            msg = (data or {}).get(\"error\") if isinstance(data, dict) else None\n            raise RuntimeError(msg or f\"bridge call {name!r} failed\")\n        return data.get(\"value\")\n\n    class _ToolCallable:\n        \"\"\"Invokes one host-side tool via the loopback HTTP bridge.\"\"\"\n\n        __slots__ = (\"_name\",)\n\n        def __init__(self, name: str):\n            self._name = name\n\n        def __repr__(self) -> str:\n            return f\"<tool.{self._name}>\"\n","sourceCodeStart":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/eval/py/prelude.py#L396-L432","documentation":"Every `tool.*` call is POSTed to the loopback bridge, which is expected to answer with JSON (`{\"ok\": ..., \"value\": ...}`). This RuntimeError is raised when the HTTP response body cannot be parsed as JSON; the first 200 bytes are quoted to identify what actually came back (e.g. an HTML error page or a proxy banner).","triggerScenarios":"The bridge port is occupied by another service returning HTML; an HTTP proxy intercepts the loopback request; the host returned a plain-text crash page; a middleware/firewall rewrites the response.","commonSituations":"Corporate proxy env vars leaking into the kernel (though the prelude builds a no-proxy opener, some environments force one); stale bridge server from a previous run listening on the port; host process dying mid-request with a non-JSON gateway response.","solutions":["Check what the quoted body is — if it's HTML, something else answered on the bridge URL; verify `PI_TOOL_BRIDGE_URL` points at the live bridge port.","Confirm the host eval process is still running and owns the port; restart the eval run.","Ensure no external proxy is intercepting loopback traffic (check `http_proxy`/`HTTPS_PROXY` and proxy tooling).","Retry the tool call once the bridge is healthy — this is often transient infrastructure, not a bad request."],"exampleFix":"// before\nresult = tool.read_file({\"path\": \"src/main.ts\"})\n\n// after\ntry:\n    result = tool.read_file({\"path\": \"src/main.ts\"})\nexcept RuntimeError as e:\n    if \"non-JSON response\" in str(e):\n        raise RuntimeError(\"tool bridge returned non-JSON; check PI_TOOL_BRIDGE_URL and that the host is alive\") from e\n    raise","handlingStrategy":"retry","validationCode":"import os, urllib.request\nbase = os.environ.get(\"PI_TOOL_BRIDGE_URL\", \"\")\nif base:\n    try:\n        urllib.request.urlopen(base, timeout=2)\n    except Exception:\n        print(\"bridge endpoint not healthy before calling tools\")","typeGuard":null,"tryCatchPattern":"import time\nfor attempt in range(3):\n    try:\n        result = tool.read_file({\"path\": p})\n        break\n    except RuntimeError as e:\n        if \"non-JSON response\" not in str(e) or attempt == 2:\n            raise\n        time.sleep(0.5 * (attempt + 1))","preventionTips":["Verify PI_TOOL_BRIDGE_URL points at the live host-owned loopback port.","Check that the host eval process is still running before long tool sequences.","Watch for proxy software intercepting loopback traffic; the prelude already disables proxies for the bridge opener.","Inspect the quoted body in the message — it usually names the culprit (HTML page, proxy banner)."],"tags":["python","network","ipc","http","json"],"backgroundTag":"non-json-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}