tinyhumansai/openhuman · error
[transport:lan] response missing result
Error message
[transport:lan] response missing result
What it means
LanHttpTransport.call() POSTs a JSON-RPC 2.0 body to the profile's rpcUrl and requires every 2xx response to carry either an `error` or an own-property `result`. This error means the body parsed as JSON but contained neither — the endpoint answered with something that is not a JSON-RPC response. It fires only after the HTTP-status check and the `json.error` check both passed, so the server returned 200 with a JSON object that has no result field.
Solutions
- Point the LAN profile's rpcUrl at the JSON-RPC endpoint, e.g. http://<lan-host>:<port>/rpc, not /health or /
- Verify with curl -X POST <rpcUrl> -d '{"jsonrpc":"2.0","id":1,"method":"openhuman.ping","params":{}}' and confirm the reply contains "result"
- Restart openhuman-core on the host (./target/debug/openhuman-core serve) so a fresh RPC listener is up, then re-enter the LAN URL
- Remove any HTTP proxy between client and LAN host, or add an exception for the core port
Example fix
// before
const profile = { kind: 'lan', rpcUrl: 'http://192.168.1.20:5800/health' };
// after
const profile = { kind: 'lan', rpcUrl: 'http://192.168.1.20:5800/rpc' }; Defensive patterns
Strategy: type-guard
Validate before calling
// Before trusting a LAN rpcUrl, confirm it speaks JSON-RPC:
async function isJsonRpcEndpoint(rpcUrl: string): Promise<boolean> {
const res = await fetch(rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'openhuman.ping', params: {} }),
});
if (!res.ok) return false;
const body = await res.json().catch(() => null);
return !!body && typeof body === 'object' && ('result' in body || 'error' in body);
} Type guard
function isJsonRpcSuccess<T>(body: unknown): body is { result: T } {
return (
typeof body === 'object' && body !== null &&
!('error' in body) &&
Object.prototype.hasOwnProperty.call(body, 'result')
);
} Try / catch
try {
await lanTransport.call('openhuman.ping', {});
} catch (err) {
if (err instanceof Error && err.message === '[transport:lan] response missing result') {
// endpoint is not speaking JSON-RPC — fix rpcUrl, do not retry blindly
}
throw err;
} Prevention
- Always store the full /rpc path in LAN profiles, never the bare host or /health
- Health-check with an openhuman.ping POST when a LAN profile is added, before persisting it
- Prefer type-guarding the parsed envelope over trusting any 200 response
When it happens
Trigger: A LAN rpcUrl that points at a non-RPC JSON endpoint (e.g. the core's GET /health returning {"status":"ok"} instead of POST /rpc); a reverse proxy or captive portal injecting its own JSON status object; the remote host running an older core binary whose response shape predates the strict result check; a port-forward landing on a different HTTP service.
Common situations: Typo'd URL when adding the LAN host (path /health or / instead of /rpc), router port-forward misconfiguration, firewall intercepting with a JSON portal page, or a core/agent version skew between the desktop host and this client.
Related errors
- LAN RPC returned an error
- [transport:local] response missing result
- Cloud RPC returned an error
- Core RPC returned an error
- MCP HTTP
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/298edcce806af82f.
Report an issue: GitHub.
Appendix: source
Thrown at app/src/services/transport/LanHttpTransport.ts:79
}
throw err;
} finally {
clearTimeout(timeoutId);
}
if (!response.ok) {
const text = await response.text();
throw new Error(`[transport:lan] HTTP ${response.status}: ${text || response.statusText}`);
}
const json = (await response.json()) as JsonRpcResponse<T>;
if (json.error) {
logErr('[transport:lan] ← %s error: %s', method, json.error.message);
throw new Error(json.error.message ?? 'LAN RPC returned an error');
}
if (!Object.prototype.hasOwnProperty.call(json, 'result')) {
throw new Error('[transport:lan] response missing result');
}
log('[transport:lan] ← %s id=%d ok', method, id);
return json.result as T;
}
async *stream<T>(
method: string,
params: unknown,
opts?: { signal?: AbortSignal }
): AsyncIterable<T> {
const result = await this.call<T>(method, params, opts);
yield result;
}
async isHealthy(): Promise<boolean> {
try {
await this.call('openhuman.ping', {}, { signal: AbortSignal.timeout(2000) });View on GitHub (pinned to 7491200858)