tinyhumansai/openhuman · error
[transport:lan] HTTP
Error message
[transport:lan] HTTP ${response.status}: ${text || response.statusText} What it means
The LAN transport's POST to the profile's rpcUrl returned non-2xx; body text (or statusText) is inlined with the status. No classification, no session-expiry handling — a plain Error. Since LAN transports send no Authorization header, 401/403 here means the remote core's network/session trust rejected the request, not a stale bearer.
Solutions
- Re-discover the desktop's current LAN address and re-save the profile (DHCP churn is the top cause)
- Check the desktop core's listener posture — if it is loopback-only, LAN profiles cannot work by design
- Probe with testCoreRpcConnection(url) (which routes through the shell relay for cleartext LAN URLs) before committing the profile
- Match the port to the core's actual RPC port for this session
Example fix
// before
const t = new LanHttpTransport('http://192.168.1.42:9100/rpc');
// after
const url = await rediscoverDesktopRpcUrl(); // resolve current mDNS/IP before constructing
const t = new LanHttpTransport(url); Defensive patterns
Strategy: validation
Validate before calling
import { testCoreRpcConnection } from '../../coreRpcClient';
async function lanProfileReachable(rpcUrl: string): Promise<boolean> {
try { return (await testCoreRpcConnection(rpcUrl)).ok; } catch { return false; }
} Type guard
function isLanHttpError(err: unknown): boolean {
return err instanceof Error && /^\[transport:lan\] HTTP \d{3}/.test(err.message);
}
function lanStatusOf(err: unknown): number | null {
const m = err instanceof Error ? err.message.match(/HTTP (\d{3})/) : null;
return m ? Number(m[1]) : null;
} Try / catch
try {
return await transport.call(method, params);
} catch (err) {
if (lanStatusOf(err) === 404) { await refreshDesktopAddress(); throw err; } // DHCP churn — re-resolve
throw err;
} Prevention
- Re-validate the profile URL whenever the network changes (SSID switch, wake from sleep)
- Remember LAN transport sends no auth header — trust is network-level; 403 means posture/allowlist config
- Prefer mDNS/stable hostnames over raw IPs in saved profiles
When it happens
Trigger: 403 from a desktop core that doesn't trust the LAN peer; 404 from a stale IP/port in the connection profile (DHCP renewal changed the desktop's address); 502/503 from intermediate tooling; connection refused variants surfacing as gateway errors.
Common situations: Profile saved with last week's LAN IP; desktop core bound to loopback only while the profile assumes LAN exposure; port changed after a core restart; phone switched networks (guest Wi-Fi isolating clients).
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- [transport:cloud] HTTP
- [transport:manager] lan profile missing rpcUrl
- LAN RPC returned an error
- [transport:lan] timed out after ms
- [transport:lan] response missing result
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/74baaa79f8851231.
Report an issue: GitHub.
Appendix: source
Thrown at app/src/services/transport/LanHttpTransport.ts:69
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')) {
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,View on GitHub (pinned to 7491200858)