can1357/oh-my-pi · error
bridge call #{name.inspect}: non-JSON response: #{resp.body.
Error message
bridge call #{name.inspect}: non-JSON response: #{resp.body.to_s[0, 200].inspect} What it means
`OmpBridge.call` POSTs a tool invocation to the host bridge and expects a JSON body. If `JSON.parse(resp.body)` fails, the bridge (or an intermediary) returned something that is not JSON — HTML error pages, plaintext, empty bodies, proxy拦截 responses. The prelude raises with the tool name and the first 200 bytes of the body quoted for diagnosis.
Source
Thrown at packages/coding-agent/src/eval/rb/prelude.rb:313
def call(name, args)
require "net/http"
require "uri"
base, token, session = proxy_env
uri = URI("#{base}/v1/tool")
payload = JSON.generate("session" => session, "run" => $__omp_current_rid, "name" => name, "args" => args)
http = Net::HTTP.new(uri.hostname, uri.port)
http.open_timeout = 10
http.read_timeout = 7 * 24 * 3600
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req["Authorization"] = "Bearer #{token}"
req.body = payload
resp = http.request(req)
data =
begin
JSON.parse(resp.body.to_s)
rescue JSON::ParserError
raise "bridge call #{name.inspect}: non-JSON response: #{resp.body.to_s[0, 200].inspect}"
end
unless data.is_a?(Hash) && data["ok"]
raise((data.is_a?(Hash) ? data["error"] : nil) || "bridge call #{name.inspect} failed")
end
data["value"]
end
def stringify_keys(hash)
out = {}
hash.each { |k, v| out[k.to_s] = v }
out
end
def tool_call(name, positional, kwargs)
merged =
if positional.nil?
{}
elsif positional.is_a?(Hash)View on GitHub (pinned to 9690622007)
Solutions
- Inspect the quoted 200-byte body in the message — an HTML doctype/403/502 page identifies who actually responded.
- Confirm PI_TOOL_BRIDGE_URL points at the live omp host bridge port (curl it with the bearer token).
- Check the host process is still running and restart the session if the bridge died.
- Bypass proxies for loopback traffic (NO_PROXY=localhost,127.0.0.1) so no intermediary mangles the response.
Example fix
// before ENV['PI_TOOL_BRIDGE_URL'] = 'http://127.0.0.1:8080' # port now served by a proxy returning HTML // after ENV['NO_PROXY'] = 'localhost,127.0.0.1' ENV['PI_TOOL_BRIDGE_URL'] = bridge_url_from_host # refreshed live port
Defensive patterns
Strategy: retry
Validate before calling
# preflight: bridge must answer with JSON
require 'net/http'
resp = Net::HTTP.post(URI("#{ENV['PI_TOOL_BRIDGE_URL'].sub(%r{/+\z},'')}/v1/tool"), '{}', 'Content-Type' => 'application/json')
JSON.parse(resp.body) # raises here if non-JSON before real work Try / catch
begin value = OmpBridge.call(name, args) rescue RuntimeError => e raise unless e.message =~ /non-JSON response/ logger.warn(e.message) # body snippet identifies the culprit value = OmpBridge.call(name, args) if transient? # limited retry after checking host end
Prevention
- Set NO_PROXY for localhost/127.0.0.1 so proxies never intercept bridge traffic.
- Confirm PI_TOOL_BRIDGE_URL port matches the live host each session.
- Alert on the quoted body snippet — HTML doctypes mean an intermediary, not the bridge, answered.
When it happens
Trigger: Calling any `tool.<name>` when the bridge server crashed and something else answers on the port, an auth proxy returns an HTML 403/502 page, the loopback URL in PI_TOOL_BRIDGE_URL points at a non-bridge service, or the response is empty/truncated at the transport layer.
Common situations: Host process died mid-session leaving the port closed/proxied; corporate proxy intercepting localhost requests; wrong PI_TOOL_BRIDGE_URL port after host restart; gateway returning 502 HTML.
Related errors
- V2 remote compaction failed (${response.status} ${response.s
- Auth broker returned malformed JSON
- 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/30da84b07380cc14.
Report an issue: GitHub.