tinyhumansai/openhuman · error

[transport:lan] timed out after ms

Error message

[transport:lan] ${method} timed out after ${this.timeoutMs}ms

What it means

LanHttpTransport.call aborts its fetch after this.timeoutMs (constructor arg, default 10_000 — a third of the cloud default) and reports it as this timeout error. As with the cloud transport, the caller's opts.signal is wired to the same AbortController, so caller-initiated cancellation is reported as a timeout too. LAN connections send no bearer header — they rely on network-level trust plus the session token in the profile — so this is purely a reachability/latency failure.

Solutions

  1. Raise the constructor timeoutMs for LAN profiles expected to run heavy RPCs (default 10s is tight)
  2. Verify the desktop is awake and the LAN address in the profile still resolves (mDNS/DHCP address changes)
  3. Distinguish caller cancellation from true timeout at the call site before reporting
  4. Retry idempotent reads once — transient Wi-Fi hiccups commonly trip the 10s budget

Example fix

// before
const t = new LanHttpTransport(rpcUrl);

// after
const t = new LanHttpTransport(rpcUrl, 30_000); // budget for cold-start RPCs over Wi-Fi
Defensive patterns

Strategy: retry

Type guard

function isLanTransportTimeout(err: unknown): boolean {
  return err instanceof Error && err.message.startsWith('[transport:lan]') && err.message.includes('timed out');
}

Try / catch

const mySignal = new AbortController().signal;
try {
  return await transport.call(method, params, { signal: mySignal });
} catch (err) {
  if (isLanTransportTimeout(err) && !mySignal.aborted && isIdempotent(method)) {
    return await transport.call(method, params);
  }
  if (mySignal.aborted) return cancelled();
  throw err;
}

Prevention

When it happens

Trigger: Any callCoreRpc routed through an active lan-http transport (iOS/remote LAN profile) exceeding 10s: Wi-Fi latency, desktop core busy, phone waking from sleep mid-call; or a caller aborting its signal, mislabeled as timeout.

Common situations: Phone on a congested network against a desktop core; desktop asleep/locked so the loopback listener stalls; default 10s too tight for first-launch heavy RPCs over LAN; component unmount aborts polluting logs.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

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

    const payload: JsonRpcRequestBody = { jsonrpc: '2.0', id, method, params: params ?? {} };

    log('[transport:lan] → %s id=%d', method, id);

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
    opts?.signal?.addEventListener('abort', () => controller.abort());

    let response: Response;
    try {
      response = await fetch(this.rpcUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
        signal: controller.signal,
      });
    } 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')) {

View on GitHub (pinned to 7491200858)