{"record":{"id":"c8d60f18fb3f1ec6","repo":"tinyhumansai/openhuman","slug":"rpc-method-returned-non-json-http-res-status-c8d60f","errorCode":null,"errorMessage":"RPC ${method} returned non-JSON HTTP ${res.status}: ${text.slice(0, 200)}","messagePattern":"RPC (.+?) returned non-JSON HTTP (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/debug/goals-live.mjs","lineNumber":206,"sourceCode":"        jsonrpc: \"2.0\",\n        id: `goals-${Date.now()}-${Math.random().toString(16).slice(2)}`,\n        method,\n        params,\n      }),\n    });\n  } catch (err) {\n    if (err?.name === \"AbortError\")\n      throw new Error(`RPC ${method} timed out after ${timeoutMs}ms`);\n    throw err;\n  } finally {\n    clearTimeout(timer);\n  }\n  const text = await res.text();\n  let body;\n  try {\n    body = JSON.parse(text);\n  } catch {\n    throw new Error(`RPC ${method} returned non-JSON HTTP ${res.status}: ${text.slice(0, 200)}`);\n  }\n  if (!res.ok) throw new Error(`RPC ${method} HTTP ${res.status}`);\n  if (body.error)\n    throw new Error(`RPC ${method} error: ${body.error.message || JSON.stringify(body.error)}`);\n  return body.result;\n}\n\n// RpcOutcome serializes either as the bare value (no logs) or { result, logs }.\nfunction unwrap(result) {\n  if (result && typeof result === \"object\" && \"result\" in result && \"logs\" in result) {\n    return { value: result.result, logs: result.logs || [] };\n  }\n  return { value: result, logs: [] };\n}\n\nfunction renderGoals(doc) {\n  const items = doc?.items || [];\n  if (items.length === 0) return \"    (no goals)\";","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/scripts/debug/goals-live.mjs#L188-L224","documentation":"After the HTTP response arrives, rpc() reads it as text and JSON.parse's it; if parsing fails it throws this error including the HTTP status and the first 200 bytes of the body. The JSON-RPC endpoint should always speak JSON, so a non-JSON body means the request never reached the real core handler — the URL hit some other server or an intermediary.","triggerScenarios":"--core-url (or OPENHUMAN_CORE_RPC_URL) pointing at a non-core server: the Vite dev server (5173), a proxy returning an HTML 502/503 page, a portal/captive page, or the core root path without /rpc; a corporate proxy intercepting 127.0.0.1 traffic; a truncated body because the process died mid-response.","commonSituations":"Port confusion between dev servers and the core's 7788 default; OPENHUMAN_CORE_RPC_URL inherited from a different session pointing at a since-restarted service; docker/network environments where the loopback proxy rewrites responses; the spawned core dying right after accepting the connection.","solutions":["Inspect the body snippet in the message — HTML usually names the offending server (nginx, Vite, a portal)","Point --core-url exactly at the core's JSON-RPC endpoint, e.g. http://127.0.0.1:7788/rpc (path /rpc included)","Bypass proxies for loopback: `NO_PROXY=127.0.0.1 no_proxy=127.0.0.1 node ...`","Sanity-check the endpoint with curl: `curl -s http://127.0.0.1:7788/health` and `GET /schema`","If the core was spawned by the script, rerun with --verbose to see whether it crashed mid-request"],"exampleFix":"// before\nOPENHUMAN_CORE_RPC_URL=http://127.0.0.1:5173/rpc node scripts/debug/goals-live.mjs\n# Error: RPC core.ping returned non-JSON HTTP 200: <!DOCTYPE html>...\n\n// after\nnode scripts/debug/goals-live.mjs --core-url http://127.0.0.1:7788/rpc","handlingStrategy":"validation","validationCode":"// verify the endpoint speaks JSON-RPC before the run\nconst res = await fetch(coreUrl, {\n  method: \"POST\",\n  headers: { \"content-type\": \"application/json\" },\n  body: JSON.stringify({ jsonrpc: \"2.0\", id: 1, method: \"core.ping\", params: {} }),\n});\nconst text = await res.text();\nlet ok = res.ok;\ntry { JSON.parse(text); } catch { ok = false; }\nif (!ok) {\n  console.error(`${coreUrl} is not a JSON-RPC endpoint (HTTP ${res.status}, body: ${text.slice(0, 120)})`);\n  process.exit(2);\n}","typeGuard":"const looksLikeJsonRpcEndpoint = (text) => {\n  try { const b = JSON.parse(text); return \"jsonrpc\" in b || \"result\" in b || \"error\" in b; }\n  catch { return false; }\n};","tryCatchPattern":"try {\n  body = JSON.parse(text);\n} catch {\n  throw new Error(`${coreUrl} returned non-JSON (HTTP ${res.status}) — wrong URL or proxy; first bytes: ${text.slice(0, 80)}`);\n}","preventionTips":["Always include the /rpc path in --core-url","Export NO_PROXY=127.0.0.1 in environments with corporate proxies","Pin the core port you own; do not reuse well-known dev ports like 5173 for the core"],"tags":["rpc","json","network","configuration"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}