{"record":{"id":"74baaa79f8851231","repo":"tinyhumansai/openhuman","slug":"transport-lan-http-response-status-text","errorCode":null,"errorMessage":"[transport:lan] HTTP ${response.status}: ${text || response.statusText}","messagePattern":"\\[transport:lan\\] HTTP (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/transport/LanHttpTransport.ts","lineNumber":69,"sourceCode":"    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')) {\n      throw new Error('[transport:lan] response missing result');\n    }\n\n    log('[transport:lan] ← %s id=%d ok', method, id);\n    return json.result as T;\n  }\n\n  async *stream<T>(\n    method: string,","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/transport/LanHttpTransport.ts#L51-L87","documentation":"The LAN peer answered with a non-2xx HTTP status; the transport embeds status plus response body (or statusText). Because LAN connections send only `Content-Type` — the Authorization header is intentionally absent, trust is network-level — a 401 here means the target is NOT the expected desktop core but something that demands auth (a router portal, another service on that port).","triggerScenarios":"404: stale rpcUrl path after the desktop's port changed; 401/403: reached some other authenticated service occupying the port; 400 from a strict middleware rejecting the JSON-RPC frame; 5xx from whatever actually listens there.","commonSituations":"DHCP gave the desktop a new IP and the old one now hosts a different device; port forwarding/reverse proxy in front of the core adding auth; the profile was hand-edited with a wrong port; captive portal on public Wi-Fi intercepting with a 3xx→200 HTML splash (surfaces as HTTP 200 + parse errors instead).","solutions":["Read the embedded status/body — it identifies what actually answered","Re-resolve the desktop's current IP:port and update the connection profile (re-pair)","Open the rpcUrl in a browser on the client device: the core's `GET /health` should answer","Remove any middlebox (proxy/portal) between client and desktop, or configure it to pass POST bodies untouched"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const u = new URL(profile.rpcUrl);\nif (!u.pathname.endsWith('/rpc')) {\n  throw new Error('LAN profile rpcUrl must point at the core /rpc path');\n}","typeGuard":"function lanHttpStatus(e: unknown): number | null {\n  if (!(e instanceof Error)) return null;\n  const m = /^\\[transport:lan\\] HTTP (\\d{3}):/.exec(e.message);\n  return m ? Number(m[1]) : null;\n}","tryCatchPattern":"try { await lan.call(m, p); }\ncatch (e) {\n  const status = lanHttpStatus(e);\n  if (status === 401 || status === 403) promptRePair(); // not our core on that port\n  else if (status === 404) refreshProfileUrl();\n  else throw e;\n}","preventionTips":["A 401 on LAN means the wrong host answered (LAN core sends no auth) — treat it as a pairing problem","Re-pair after the desktop's network changes instead of hand-editing IPs","Parse the embedded status code from the message to branch recovery logic"],"tags":["http","lan","network","transport","connection-profile"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}