{"record":{"id":"5c7bb8e662cd0087","repo":"tinyhumansai/openhuman","slug":"cloud-rpc-returned-an-error","errorCode":null,"errorMessage":"Cloud RPC returned an error","messagePattern":"Cloud RPC returned an error","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/transport/CloudHttpTransport.ts","lineNumber":82,"sourceCode":"    } catch (err) {\n      if (controller.signal.aborted) {\n        throw new Error(`[transport:cloud] ${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:cloud] 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:cloud] ← %s error: %s', method, json.error.message);\n      throw new Error(json.error.message ?? 'Cloud RPC returned an error');\n    }\n    if (!Object.prototype.hasOwnProperty.call(json, 'result')) {\n      throw new Error('[transport:cloud] response missing result');\n    }\n\n    log('[transport:cloud] ← %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":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/transport/CloudHttpTransport.ts#L64-L100","documentation":"Cloud-transport sibling of the core client's error fallback: the JSON parsed fine and contained a JSON-RPC `error` object, but `json.error.message` was nullish, so the literal 'Cloud RPC returned an error' is thrown. The real diagnostic was logged just before via `logErr('[transport:cloud] ← %s error: %s', ...)` but is not propagated on the thrown value — unlike `CoreRpcError`, this is a plain `Error` with no kind/data.","triggerScenarios":"Cloud core replying `{error: {code: N}}` with no message — server-side error structs that omit message, or gateway-mangled frames that keep the error envelope but drop fields.","commonSituations":"Version skew between the client's expected error shape and the cloud core's serialization; debugging blind because the thrown message carries no code — the code is only visible in debug logs.","solutions":["Enable `DEBUG=transport:cloud:error` to see the raw json.error (message/code) for the failing method","Check the cloud core's own logs for the request id at that timestamp","If you control the server, always serialize a non-empty error.message","Match client/server versions to eliminate shape drift"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await cloud.call(m, p); }\ncatch (e) {\n  if (e instanceof Error && e.message === 'Cloud RPC returned an error') {\n    // message was empty — pull details from debug logs, then surface a generic banner\n    logger.warn('empty cloud error frame', { method: m });\n  }\n  throw e;\n}","preventionTips":["Run dev builds with DEBUG=transport:cloud:error so empty error frames are still recorded with their code","If you own the server, enforce non-empty error messages in the RPC layer","Pin client and cloud-core versions together to avoid error-envelope drift"],"tags":["json-rpc","cloud","error-handling","transport"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}