{"record":{"id":"16485feffbff1998","repo":"tinyhumansai/openhuman","slug":"lan-rpc-returned-an-error","errorCode":null,"errorMessage":"LAN RPC returned an error","messagePattern":"LAN RPC returned an error","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/transport/LanHttpTransport.ts","lineNumber":76,"sourceCode":"    } 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,\n    params: unknown,\n    opts?: { signal?: AbortSignal }\n  ): AsyncIterable<T> {\n    const result = await this.call<T>(method, params, opts);\n    yield result;\n  }\n","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/transport/LanHttpTransport.ts#L58-L94","documentation":"LAN transport's empty-message fallback: the JSON-RPC frame contained an `error` object but `json.error.message` was nullish, so the generic literal is thrown as a plain `Error`. The real error code/message was logged via `logErr('[transport:lan] ← %s error: %s', ...)` and lost to the exception — there is no CoreRpcError-style kind/data propagation on this path.","triggerScenarios":"Desktop core returning an error frame with a missing/blank message (error struct serialization dropping the field), or a version-skewed core whose error envelope changed shape while keeping the `error` key.","commonSituations":"Older desktop core paired with a newer client; debugging blind spots because the thrown message carries no code — only `DEBUG=transport:lan:error` reveals it.","solutions":["Run with `DEBUG=transport:lan:error` and reproduce — the raw json.error (with code) is logged","Check the desktop core's log for the matching request id","Update the desktop app / core so both ends share the current error envelope","If you control handlers, never serialize an error without a non-empty message"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await lan.call(m, p); }\ncatch (e) {\n  if (e instanceof Error && e.message === 'LAN RPC returned an error') {\n    logger.warn('empty LAN error frame', { method: m }); // details only in DEBUG=transport:lan:error\n  }\n  throw e;\n}","preventionTips":["Enable DEBUG=transport:lan:error during LAN development — empty error frames are logged with their code","Keep client and desktop core versions paired together","If you implement core handlers, lint against serializing errors with empty messages"],"tags":["json-rpc","lan","error-handling","transport"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}