{"record":{"id":"0be67cfefbc61ffd","repo":"tinyhumansai/openhuman","slug":"core-rpc-returned-an-error-0be67c","errorCode":null,"errorMessage":"Core RPC returned an error","messagePattern":"Core RPC returned an error","errorType":"exception","errorClass":"CoreRpcError","httpStatus":null,"severity":"error","filePath":"app/src/services/coreRpcClient.ts","lineNumber":760,"sourceCode":"          payload.method,\n          classifyAuthExpiredReason(text || response.statusText, response.status)\n        );\n      throw new CoreRpcError(httpMessage, kind, response.status);\n    }\n\n    const json = (await response.json()) as JsonRpcResponse<T>;\n\n    if (json.error) {\n      coreRpcError('HTTP error response', {\n        id: payload.id,\n        method: payload.method,\n        error: json.error,\n      });\n      const rawMessage = json.error.message || 'Core RPC returned an error';\n      const kind = classifyRpcError(rawMessage, undefined, json.error.data);\n      if (kind === 'auth_expired' && !suppressAuthExpiredEvent)\n        dispatchAuthExpired(payload.method, classifyAuthExpiredReason(rawMessage, undefined));\n      throw new CoreRpcError(rawMessage, kind, undefined, json.error.data);\n    }\n    if (!Object.prototype.hasOwnProperty.call(json, 'result')) {\n      throw new Error('Core RPC response missing result');\n    }\n\n    coreRpcLog('HTTP response', { id: payload.id, method: payload.method });\n    return json.result as T;\n  } catch (err) {\n    coreRpcError('Core RPC call failed', sanitizeError(err));\n    if (err instanceof CoreRpcError) throw err;\n    const message = coreRpcErrorMessage(err);\n    throw new CoreRpcError(message, classifyRpcError(message));\n  }\n}\n","sourceCodeStart":742,"sourceCodeEnd":775,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/coreRpcClient.ts#L742-L775","documentation":"Fallback message used when the embedded Rust core answers a JSON-RPC call with an `error` object whose `message` field is missing or empty (`json.error.message || 'Core RPC returned an error'`). The actual failure happened core-side in a controller handler; this string only stands in for an empty message. The thrown value is a `CoreRpcError` that also carries `kind` (from `classifyRpcError(rawMessage, undefined, json.error.data)`) and the raw `json.error.data`, which usually contain the real diagnosis even when the message is blank.","triggerScenarios":"Any `coreRpcClient.call` / `callCoreRpc` invocation where the core replies HTTP 200 with `{ \"error\": { \"code\": N, \"message\": \"\" } }` or an error object with no `message` key — e.g. a Rust controller returning an error struct whose message serializes to null, or a serde error mapped to a bare code. The code path first logs the full `json.error` at `core-rpc:error` as 'HTTP error response' with the request id and method.","commonSituations":"Frontend/core version skew (stale core binary lacking a new method), session expiry where the reason rides in `error.data.kind`, controllers that return `Err` with a unit or empty-string message, auth rejection from the bearer-token gate on `/rpc`.","solutions":["Inspect the caught `CoreRpcError`'s `kind` and `data` fields — the message can be empty while `data` holds a typed kind (e.g. `{ kind: 'ThreadNotFound' }`)","Enable the debug logger (`DEBUG=core-rpc:error` or `debug.enable('core-rpc:error')`) and reproduce — the full `json.error` object is logged with id/method before the throw","Check the core-side log file for the same timestamp; the Rust handler usually logs the underlying cause","Rebuild/restart the core (`pnpm dev:app` or `restart_core_process`) so the controller set matches the frontend expectations"],"exampleFix":"// before\ntry { await coreRpcClient.call('openhuman.memory_search', params); }\ncatch (e) { toast(String(e)); }\n\n// after\nimport { CoreRpcError } from '../services/coreRpcClient';\ntry { await coreRpcClient.call('openhuman.memory_search', params); }\ncatch (e) {\n  if (e instanceof CoreRpcError) {\n    // data carries the typed reason even when message is the empty fallback\n    toast(e.data ? JSON.stringify(e.data) : e.message, e.kind);\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"import { CoreRpcError } from '../services/coreRpcClient';\n\nfunction isCoreRpcError(e: unknown): e is CoreRpcError {\n  return e instanceof CoreRpcError;\n}","tryCatchPattern":"try {\n  const r = await coreRpcClient.call('openhuman.x_y', params);\n} catch (e) {\n  if (e instanceof CoreRpcError) {\n    // message may be the empty fallback — prefer data/kind\n    report({ kind: e.kind, data: e.data, message: e.message });\n    if (e.kind === 'auth_expired') reAuth();\n    return;\n  }\n  throw e;\n}","preventionTips":["Always read CoreRpcError.kind and .data, never .message alone — the message can be the empty fallback while data holds the typed reason","Keep DEBUG=core-rpc:error enabled in dev builds; it logs the full json.error frame before throwing","Keep frontend and core versions in lockstep (pnpm dev:app builds both) to avoid unknown-method errors"],"tags":["json-rpc","core-rpc","error-handling","tauri"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}