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

  1. Point the LAN profile's rpcUrl at the JSON-RPC endpoint, e.g. http://<lan-host>:<port>/rpc, not /health or /
  2. Verify with curl -X POST <rpcUrl> -d '{"jsonrpc":"2.0","id":1,"method":"openhuman.ping","params":{}}' and confirm the reply contains "result"
  3. Restart openhuman-core on the host (./target/debug/openhuman-core serve) so a fresh RPC listener is up, then re-enter the LAN URL
  4. 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

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


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)