tinyhumansai/openhuman · error · Error
RPC ${method} returned non-JSON HTTP ${res.status}: ${text.s
Error message
RPC ${method} returned non-JSON HTTP ${res.status}: ${text.slice(0, 200)} What it means
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.
Source
Thrown at scripts/debug/goals-live.mjs:206
jsonrpc: "2.0",
id: `goals-${Date.now()}-${Math.random().toString(16).slice(2)}`,
method,
params,
}),
});
} catch (err) {
if (err?.name === "AbortError")
throw new Error(`RPC ${method} timed out after ${timeoutMs}ms`);
throw err;
} finally {
clearTimeout(timer);
}
const text = await res.text();
let body;
try {
body = JSON.parse(text);
} catch {
throw new Error(`RPC ${method} returned non-JSON HTTP ${res.status}: ${text.slice(0, 200)}`);
}
if (!res.ok) throw new Error(`RPC ${method} HTTP ${res.status}`);
if (body.error)
throw new Error(`RPC ${method} error: ${body.error.message || JSON.stringify(body.error)}`);
return body.result;
}
// RpcOutcome serializes either as the bare value (no logs) or { result, logs }.
function unwrap(result) {
if (result && typeof result === "object" && "result" in result && "logs" in result) {
return { value: result.result, logs: result.logs || [] };
}
return { value: result, logs: [] };
}
function renderGoals(doc) {
const items = doc?.items || [];
if (items.length === 0) return " (no goals)";View on GitHub (pinned to a221052e0d)
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
Example fix
// before OPENHUMAN_CORE_RPC_URL=http://127.0.0.1:5173/rpc node scripts/debug/goals-live.mjs # Error: RPC core.ping returned non-JSON HTTP 200: <!DOCTYPE html>... // after node scripts/debug/goals-live.mjs --core-url http://127.0.0.1:7788/rpc
Defensive patterns
Strategy: validation
Validate before calling
// verify the endpoint speaks JSON-RPC before the run
const res = await fetch(coreUrl, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "core.ping", params: {} }),
});
const text = await res.text();
let ok = res.ok;
try { JSON.parse(text); } catch { ok = false; }
if (!ok) {
console.error(`${coreUrl} is not a JSON-RPC endpoint (HTTP ${res.status}, body: ${text.slice(0, 120)})`);
process.exit(2);
} Type guard
const looksLikeJsonRpcEndpoint = (text) => {
try { const b = JSON.parse(text); return "jsonrpc" in b || "result" in b || "error" in b; }
catch { return false; }
}; Try / catch
try {
body = JSON.parse(text);
} catch {
throw new Error(`${coreUrl} returned non-JSON (HTTP ${res.status}) — wrong URL or proxy; first bytes: ${text.slice(0, 80)}`);
} Prevention
- 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
When it happens
Trigger: --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.
Common situations: 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.
Related errors
- RPC ${method} returned non-JSON HTTP ${res.status}
- Invalid ${paramName}: '${value}'. Must be a valid integer ID
- Invalid ${paramName}: ${String(value)}. Type must be an inte
- RPC ${method} timed out after ${timeoutMs}ms
- RPC ${method} timed out after ${timeoutMs}ms
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/c8d60f18fb3f1ec6.
Report an issue: GitHub.