tinyhumansai/openhuman · error · Error

[transport:local] response missing result

Error message

[transport:local] response missing result

What it means

HTTP 200 from the local endpoint, but the parsed JSON has neither an `error` nor an own `result` property (checked via hasOwnProperty) — the body is not a JSON-RPC envelope at all. The transport refuses to guess and aborts.

Source

Thrown at app/src/services/transport/LocalTransport.ts:87

      }
      throw err;
    } finally {
      clearTimeout(timeoutId);
    }

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`[transport:local] HTTP ${response.status}: ${text || response.statusText}`);
    }

    const json = (await response.json()) as JsonRpcResponse<T>;

    if (json.error) {
      logErr('[transport:local] ← %s error: %s', method, json.error.message);
      throw new Error(json.error.message ?? 'Core RPC returned an error');
    }
    if (!Object.prototype.hasOwnProperty.call(json, 'result')) {
      throw new Error('[transport:local] response missing result');
    }

    log('[transport:local] ← %s id=%d ok', method, id);
    return json.result as T;
  }

  async *stream<T>(
    method: string,
    params: unknown,
    opts?: { signal?: AbortSignal }
  ): AsyncIterable<T> {
    // Local HTTP doesn't support streaming natively in this project.
    // Fall back to a single call and yield the result.
    const result = await this.call<T>(method, params, opts);
    yield result;
  }

  async isHealthy(): Promise<boolean> {

View on GitHub (pinned to a221052e0d)

Solutions

  1. Log the raw response text before parsing to see what actually came back
  2. Verify localRpcUrl is the exact /rpc path of the core's HTTP host
  3. Ensure the request body is a well-formed JSON-RPC request with an id (notifications legitimately get no result)
  4. Exclude /rpc from any proxy/middleware that rewrites response bodies

Example fix

// before
const json = (await response.json()) as JsonRpcResponse<T>;

// after — surface what the 200 actually contained
const text = await response.text();
let json: JsonRpcResponse<T>;
try {
  json = JSON.parse(text);
} catch {
  throw new Error(`[transport:local] non-JSON 200 body: ${text.slice(0, 200)}`);
}
Defensive patterns

Strategy: try-catch

Type guard

function isJsonRpcEnvelope(v: unknown): v is { result?: unknown; error?: { code?: number; message?: string } } {
  return typeof v === 'object' && v !== null && ('result' in v || 'error' in v);
}

Try / catch

Catch, then immediately log which URL was called — this error almost always means wrong endpoint or a rewriting proxy, not a flaky core; fix the endpoint instead of retrying.

Prevention

When it happens

Trigger: A 200 whose JSON is an empty object, a bare value, or middleware's own JSON (e.g. an auth layer returning {ok:false}); localRpcUrl pointing at the wrong endpoint such as /health instead of /rpc; a gateway rewriting the body.

Common situations: localRpcUrl misconfigured to a non-/rpc path; an external core behind a reverse proxy that rewrites or wraps response bodies; a mock server configured for a different method; a notification-style request (no id) getting an id-less reply.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/cc39a7bd97153e65. Report an issue: GitHub.