can1357/oh-my-pi · error · RuntimeError
bridge call {name!r}: non-JSON response: {body[:200]!r}
Error message
bridge call {name!r}: non-JSON response: {body[:200]!r} What it means
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).
Source
Thrown at packages/coding-agent/src/eval/py/prelude.py:414
).encode("utf-8")
req = urllib.request.Request(
f"{base}/v1/tool",
data=payload,
method="POST",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
},
)
try:
with _BRIDGE_OPENER.open(req) as resp:
body = resp.read()
except urllib.error.HTTPError as exc:
body = exc.read()
try:
data = json.loads(body)
except json.JSONDecodeError:
raise RuntimeError(
f"bridge call {name!r}: non-JSON response: {body[:200]!r}"
) from None
if not isinstance(data, dict) or not data.get("ok"):
msg = (data or {}).get("error") if isinstance(data, dict) else None
raise RuntimeError(msg or f"bridge call {name!r} failed")
return data.get("value")
class _ToolCallable:
"""Invokes one host-side tool via the loopback HTTP bridge."""
__slots__ = ("_name",)
def __init__(self, name: str):
self._name = name
def __repr__(self) -> str:
return f"<tool.{self._name}>"
View on GitHub (pinned to 9690622007)
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.
Example fix
// before
result = tool.read_file({"path": "src/main.ts"})
// after
try:
result = tool.read_file({"path": "src/main.ts"})
except RuntimeError as e:
if "non-JSON response" in str(e):
raise RuntimeError("tool bridge returned non-JSON; check PI_TOOL_BRIDGE_URL and that the host is alive") from e
raise Defensive patterns
Strategy: retry
Validate before calling
import os, urllib.request
base = os.environ.get("PI_TOOL_BRIDGE_URL", "")
if base:
try:
urllib.request.urlopen(base, timeout=2)
except Exception:
print("bridge endpoint not healthy before calling tools") Try / catch
import time
for attempt in range(3):
try:
result = tool.read_file({"path": p})
break
except RuntimeError as e:
if "non-JSON response" not in str(e) or attempt == 2:
raise
time.sleep(0.5 * (attempt + 1)) Prevention
- 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).
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- bridge call {name!r} failed
- V2 remote compaction failed (${response.status} ${response.s
- sso-role
- HTTP request failed. status=${response.status}; url=${url};
- ${response.status} ${response.statusText}: ${text}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/0565ba8e19e66bf5.
Report an issue: GitHub.