{"record":{"id":"30da84b07380cc14","repo":"can1357/oh-my-pi","slug":"bridge-call-name-inspect-non-json-response","errorCode":null,"errorMessage":"bridge call #{name.inspect}: non-JSON response: #{resp.body.to_s[0, 200].inspect}","messagePattern":"bridge call #(.+?): non-JSON response: #(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/eval/rb/prelude.rb","lineNumber":313,"sourceCode":"    def call(name, args)\n      require \"net/http\"\n      require \"uri\"\n      base, token, session = proxy_env\n      uri = URI(\"#{base}/v1/tool\")\n      payload = JSON.generate(\"session\" => session, \"run\" => $__omp_current_rid, \"name\" => name, \"args\" => args)\n      http = Net::HTTP.new(uri.hostname, uri.port)\n      http.open_timeout = 10\n      http.read_timeout = 7 * 24 * 3600\n      req = Net::HTTP::Post.new(uri)\n      req[\"Content-Type\"] = \"application/json\"\n      req[\"Authorization\"] = \"Bearer #{token}\"\n      req.body = payload\n      resp = http.request(req)\n      data =\n        begin\n          JSON.parse(resp.body.to_s)\n        rescue JSON::ParserError\n          raise \"bridge call #{name.inspect}: non-JSON response: #{resp.body.to_s[0, 200].inspect}\"\n        end\n      unless data.is_a?(Hash) && data[\"ok\"]\n        raise((data.is_a?(Hash) ? data[\"error\"] : nil) || \"bridge call #{name.inspect} failed\")\n      end\n      data[\"value\"]\n    end\n\n    def stringify_keys(hash)\n      out = {}\n      hash.each { |k, v| out[k.to_s] = v }\n      out\n    end\n\n    def tool_call(name, positional, kwargs)\n      merged =\n        if positional.nil?\n          {}\n        elsif positional.is_a?(Hash)","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/eval/rb/prelude.rb#L295-L331","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nENV['PI_TOOL_BRIDGE_URL'] = 'http://127.0.0.1:8080'  # port now served by a proxy returning HTML\n// after\nENV['NO_PROXY'] = 'localhost,127.0.0.1'\nENV['PI_TOOL_BRIDGE_URL'] = bridge_url_from_host  # refreshed live port","handlingStrategy":"retry","validationCode":"# preflight: bridge must answer with JSON\nrequire 'net/http'\nresp = Net::HTTP.post(URI(\"#{ENV['PI_TOOL_BRIDGE_URL'].sub(%r{/+\\z},'')}/v1/tool\"), '{}', 'Content-Type' => 'application/json')\nJSON.parse(resp.body) # raises here if non-JSON before real work","typeGuard":null,"tryCatchPattern":"begin\n  value = OmpBridge.call(name, args)\nrescue RuntimeError => e\n  raise unless e.message =~ /non-JSON response/\n  logger.warn(e.message) # body snippet identifies the culprit\n  value = OmpBridge.call(name, args) if transient? # limited retry after checking host\nend","preventionTips":["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."],"tags":["ruby","http","json-parse","network","tool-bridge"],"backgroundTag":"non-json-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}