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

  1. 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.
  2. Confirm the host eval process is still running and owns the port; restart the eval run.
  3. Ensure no external proxy is intercepting loopback traffic (check `http_proxy`/`HTTPS_PROXY` and proxy tooling).
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/0565ba8e19e66bf5. Report an issue: GitHub.