tinyhumansai/openhuman · error · CoreRpcError

Unknown core RPC error

Error message

Unknown core RPC error

What it means

Catch-all message produced by `coreRpcErrorMessage(err)` at the bottom of the call wrapper: it is returned only when the caught value is not an `Error` with a message, not a string, and not an object with a usable `message`/`error` string property. So this error means something inside the RPC pipeline threw a non-error value (`undefined`, `null`, a number, a plain `{}`), not that the core reported anything. The rethrown value is a `CoreRpcError` whose kind comes from `classifyRpcError('Unknown core RPC error')` (i.e. 'unknown').

Source

Thrown at app/src/services/coreRpcClient.ts:772

        error: json.error,
      });
      const rawMessage = json.error.message || 'Core RPC returned an error';
      const kind = classifyRpcError(rawMessage, undefined, json.error.data);
      if (kind === 'auth_expired' && !suppressAuthExpiredEvent)
        dispatchAuthExpired(payload.method, classifyAuthExpiredReason(rawMessage, undefined));
      throw new CoreRpcError(rawMessage, kind, undefined, json.error.data);
    }
    if (!Object.prototype.hasOwnProperty.call(json, 'result')) {
      throw new Error('Core RPC response missing result');
    }

    coreRpcLog('HTTP response', { id: payload.id, method: payload.method });
    return json.result as T;
  } catch (err) {
    coreRpcError('Core RPC call failed', sanitizeError(err));
    if (err instanceof CoreRpcError) throw err;
    const message = coreRpcErrorMessage(err);
    throw new CoreRpcError(message, classifyRpcError(message));
  }
}

View on GitHub (pinned to a221052e0d)

Solutions

  1. Look at the preceding `core-rpc:error` 'Core RPC call failed' log line — it prints `sanitizeError(err)`, the raw caught value, which identifies the throw site
  2. Grep the codebase for `throw {` and bare `Promise.reject(` and convert them to `throw new Error(...)`
  3. Reproduce with the debugger paused on the catch to see the actual value of `err`
  4. If the non-Error comes from a dependency, wrap that call in try/catch and rethrow a real Error

Example fix

// before (meetCallService.ts pattern)
throw { message: 'Please paste a meeting link.', isCapacityGated: false };

// after
class CapacityGateError extends Error {
  constructor(msg, readonly isCapacityGated: boolean) { super(msg); }
}
throw new CapacityGateError('Please paste a meeting link.', false);
Defensive patterns

Strategy: try-catch

Type guard

function hasMessage(e: unknown): e is { message: string } {
  return !!e && typeof e === 'object' && typeof (e as any).message === 'string' && (e as any).message.length > 0;
}

Try / catch

try { await coreRpcClient.call(m, p); }
catch (e) {
  const msg = e instanceof Error ? e.message
    : typeof e === 'string' ? e
    : hasMessage(e) ? e.message : 'call failed (non-error thrown)';
  // always log the raw value too — it identifies a `throw {}` site
  console.warn('raw rejection:', e);
  throw new Error(msg);
}

Prevention

When it happens

Trigger: A promise in the pipeline rejects with `undefined` (e.g. an aborted deferred, a `.then()` chain that implicitly returns undefined on a rejection path), or code throws a plain object — note the codebase itself has one: `joinMeetingViaMascotBot` in meetCallService.ts throws `{ message, isCapacityGated }`, which `coreRpcErrorMessage` can rescue only if `message` is non-empty; `{}` or `throw undefined` hits the fallback.

Common situations: Third-party or sibling code doing `throw {}` / `Promise.reject()` without a reason; an abort/cancel path that rejects with a non-Error; a fetch polyfill rejecting with an event object instead of a TypeError.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/f13b9720a521f540. Report an issue: GitHub.