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

  1. Inspect the quoted 200-byte body in the message — an HTML doctype/403/502 page identifies who actually responded.
  2. Confirm PI_TOOL_BRIDGE_URL points at the live omp host bridge port (curl it with the bearer token).
  3. Check the host process is still running and restart the session if the bridge died.
  4. 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

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


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