{"record":{"id":"9cd46846cdb8114e","repo":"tinyhumansai/openhuman","slug":"transport-lan-method-timed-out-after-this-t","errorCode":null,"errorMessage":"[transport:lan] ${method} timed out after ${this.timeoutMs}ms","messagePattern":"\\[transport:lan\\] (.+?) timed out after (.+?)ms","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/transport/LanHttpTransport.ts","lineNumber":60,"sourceCode":"    const payload: JsonRpcRequestBody = { jsonrpc: '2.0', id, method, params: params ?? {} };\n\n    log('[transport:lan] → %s id=%d', method, id);\n\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n    opts?.signal?.addEventListener('abort', () => controller.abort());\n\n    let response: Response;\n    try {\n      response = await fetch(this.rpcUrl, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify(payload),\n        signal: controller.signal,\n      });\n    } catch (err) {\n      if (controller.signal.aborted) {\n        throw new Error(`[transport:lan] ${method} timed out after ${this.timeoutMs}ms`);\n      }\n      throw err;\n    } finally {\n      clearTimeout(timeoutId);\n    }\n\n    if (!response.ok) {\n      const text = await response.text();\n      throw new Error(`[transport:lan] HTTP ${response.status}: ${text || response.statusText}`);\n    }\n\n    const json = (await response.json()) as JsonRpcResponse<T>;\n\n    if (json.error) {\n      logErr('[transport:lan] ← %s error: %s', method, json.error.message);\n      throw new Error(json.error.message ?? 'LAN RPC returned an error');\n    }\n    if (!Object.prototype.hasOwnProperty.call(json, 'result')) {","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/services/transport/LanHttpTransport.ts#L42-L78","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Raise the constructor timeoutMs for LAN profiles expected to run heavy RPCs (default 10s is tight)","Verify the desktop is awake and the LAN address in the profile still resolves (mDNS/DHCP address changes)","Distinguish caller cancellation from true timeout at the call site before reporting","Retry idempotent reads once — transient Wi-Fi hiccups commonly trip the 10s budget"],"exampleFix":"// before\nconst t = new LanHttpTransport(rpcUrl);\n\n// after\nconst t = new LanHttpTransport(rpcUrl, 30_000); // budget for cold-start RPCs over Wi-Fi","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isLanTransportTimeout(err: unknown): boolean {\n  return err instanceof Error && err.message.startsWith('[transport:lan]') && err.message.includes('timed out');\n}","tryCatchPattern":"const mySignal = new AbortController().signal;\ntry {\n  return await transport.call(method, params, { signal: mySignal });\n} catch (err) {\n  if (isLanTransportTimeout(err) && !mySignal.aborted && isIdempotent(method)) {\n    return await transport.call(method, params);\n  }\n  if (mySignal.aborted) return cancelled();\n  throw err;\n}","preventionTips":["The LAN default is only 10s — raise timeoutMs for heavy RPCs over Wi-Fi","Verify the desktop is awake and its LAN address is current before long sessions","Caller aborts are reported as timeouts here — always check your own signal first"],"tags":["transport","lan","timeout","abort-controller","ios","wifi"],"backgroundTag":"request-timeout","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}