tinyhumansai/openhuman · error

LAN RPC returned an error

Error message

LAN RPC returned an error

What it means

The LAN core answered 2xx with a JSON-RPC error envelope whose message was null/undefined, so the generic 'LAN RPC returned an error' text is used. The debug line ('[transport:lan] ← %s error') logged json.error.message just before, and json.error.code is available on the wire frame even though this plain Error drops it.

Solutions

  1. Enable DEBUG='transport:lan:error' to capture the logged json.error.message/code pair
  2. Check the desktop core's /schema for the method before the mobile UI calls it
  3. Update the desktop app so both ends share the method surface
  4. Gate UI features on a capability/schema probe rather than assuming method availability

Example fix

// before
const r = await transport.call('openhuman.meet_list_upcoming', {});

// after
const schema = await transport.call<{ methods: string[] }>('core.schema', {});
if (!schema.methods.includes('openhuman.meet_list_upcoming')) throw new Error('meet not available on this core');
const r = await transport.call('openhuman.meet_list_upcoming', {});
Defensive patterns

Strategy: try-catch

Validate before calling

async function lanCoreHasMethod(transport: LanHttpTransport, method: string): Promise<boolean> {
  const schema = await transport.call<{ methods: string[] }>('core.schema', {});
  return schema.methods.includes(method);
}

Type guard

function isLanEnvelopeError(err: unknown): boolean {
  return err instanceof Error && (err.message === 'LAN RPC returned an error' || err.message.startsWith('[transport:lan]'));
}

Try / catch

try {
  return await transport.call<T>(method, params);
} catch (err) {
  if (isLanEnvelopeError(err)) {
    // DEBUG='transport:lan:error' logged json.error.code/message — correlate before reporting
    logger.error('LAN core rejected call', { method });
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling a method the desktop core doesn't register while on a LAN profile (gated domain off in that core build, or app/core version skew); server-side validation failures whose handler omits the message field.

Common situations: iOS client newer than the desktop build (method added later); desktop running a slim feature set (--no-default-features) that omitted a family the mobile UI expects; drifted method names between versions.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/16485feffbff1998. Report an issue: GitHub.

Appendix: source

Thrown at app/src/services/transport/LanHttpTransport.ts:76

    } catch (err) {
      if (controller.signal.aborted) {
        throw new Error(`[transport:lan] ${method} timed out after ${this.timeoutMs}ms`);
      }
      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;
  }

View on GitHub (pinned to 7491200858)