tinyhumansai/openhuman · error
[transport:lan] ${method} timed out after ${this.timeoutMs}m
Error message
[transport:lan] ${method} timed out after ${this.timeoutMs}ms What it means
LAN-transport timeout: `fetch` rejected and the shared controller was aborted — by the internal timer (default 10s, deliberately shorter than cloud/local) or by the caller's signal, which the `controller.signal.aborted` check cannot distinguish. This transport connects a remote client (e.g. the iOS app) to the desktop core over the local network with no Authorization header (network-level trust per the class docs), so a timeout almost always means the desktop core was unreachable or slow.
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 a221052e0d)
Solutions
- Verify both devices are on the same network and the desktop core is running (its URL answers `GET /health`)
- Re-pair/re-fetch the connection profile so rpcUrl matches the desktop's current IP:port
- Allow the port through the desktop firewall; ensure the core's listener posture covers LAN (a deliberate user decision, not default)
- Raise `timeoutMs` in the LanHttpTransport constructor for known-slow calls, and retry idempotent ones with backoff
Example fix
// before
const t = new LanHttpTransport(profile.rpcUrl); // 10s default
await t.call('openhuman.memory_search', q);
// after
const t = new LanHttpTransport(profile.rpcUrl, 30_000);
await withRetry(() => t.call('openhuman.memory_search', q), { tries: 3 }); Defensive patterns
Strategy: retry
Validate before calling
async function lanReachable(rpcUrl: string): Promise<boolean> {
try {
const r = await fetch(new URL('/health', rpcUrl).toString(), { signal: AbortSignal.timeout(3000) });
return r.ok;
} catch { return false; }
} Type guard
function isLanTimeout(e: unknown): boolean {
return e instanceof Error && e.message.startsWith('[transport:lan]') && e.message.includes('timed out');
} Try / catch
if (!(await lanReachable(profile.rpcUrl))) {
throw new Error('Desktop core unreachable — check both devices share the network.');
}
for (let a = 1; a <= 3; a++) {
try { return await lan.call(m, p); }
catch (e) { if (!isLanTimeout(e) || a === 3) throw e; await sleep(2 ** a * 300); }
} Prevention
- Prefer stable hostnames/mDNS over raw IPs in LAN connection profiles to survive DHCP changes
- Health-check the desktop core before first RPC of a session
- Remember the 10s default is the shortest of the three transports — raise it for heavy methods
- Caller-signal aborts are reported as timeouts; check your own cancellation first
When it happens
Trigger: iOS device and desktop on different networks/VLANs; desktop core not running or its listener bound to loopback only; phone Wi-Fi with poor signal to the desktop host; the 10s default being too tight for a heavy method over Wi-Fi; caller cancellation mislabeled as timeout.
Common situations: Connection profile's rpcUrl host/port stale after the desktop's IP changed (DHCP); firewall on the desktop blocking the port; desktop asleep; user switched from home to public Wi-Fi which isolates clients.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- [transport:cloud] ${method} timed out after ${this.timeoutMs
- [transport:lan] HTTP ${response.status}: ${text || response.
- Request timed out. Please try again.
- [transport:cloud] HTTP ${response.status}: ${text || respons
- LAN RPC returned an error
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/9cd46846cdb8114e.
Report an issue: GitHub.