tinyhumansai/openhuman · error · CoreRpcError

Core RPC timed out after ms

Error message

Core RPC ${payload.method} timed out after ${effectiveTimeoutMs}ms

What it means

A fully-classified CoreRpcError with kind 'timeout': the AbortController that bounds every core RPC fetch fired before the response arrived. The budget is effectiveTimeoutMs = resolvePerCallTimeoutMs(timeoutMs) — the default CORE_RPC_TIMEOUT_MS unless the caller passes a per-call override, clamped to 1s..10min. It is deliberately a CoreRpcError (not a bare Error) so the outer catch does not re-wrap it and callers can branch on err.kind === 'timeout' (Sentry filtering, soft-toast skipping).

Solutions

  1. Pass a per-call budget for known-slow RPCs: callCoreRpc({ method, params, timeoutMs: 90_000 }) — mirroring the app_state_snapshot fix in #2156
  2. Check whether the core process is alive and responsive (health endpoint / platform health RPC) and restart it if hung
  3. Branch on err.kind === 'timeout' to retry idempotent reads once instead of surfacing a hard error
  4. Inspect core-side logs to find what made the handler exceed the default budget (lock contention, migration, cold cache)

Example fix

// before
const snap = await callCoreRpc<AppStateSnapshot>({ method: 'openhuman.app_state_snapshot' });

// after
const snap = await callCoreRpc<AppStateSnapshot>({
  method: 'openhuman.app_state_snapshot',
  timeoutMs: 90_000, // first-launch snapshot budget (#2156)
});
Defensive patterns

Strategy: retry

Type guard

import { CoreRpcError } from '../services/coreRpcClient';

function isCoreRpcTimeout(err: unknown): boolean {
  return err instanceof CoreRpcError && err.kind === 'timeout';
}

Try / catch

try {
  return await callCoreRpc({ method, params, timeoutMs: 90_000 });
} catch (err) {
  if (isCoreRpcTimeout(err) && isIdempotentRead(method)) {
    return await callCoreRpc({ method, params, timeoutMs: 90_000 }); // one bounded retry
  }
  throw err;
}

Prevention

When it happens

Trigger: Core takes longer than the budget: a hung/deadlocked core process, first-launch app_state_snapshot with cold stores (#2156 raised that path to 90s via timeoutMs), heavy migrations on boot, or a too-low per-call timeoutMs being clamped. Any callCoreRpc(method) where the core does not answer within effectiveTimeoutMs.

Common situations: Cold-start workloads on large workspaces; a core blocked on a lock or stuck subprocess; dev machines with slow disks; passing timeoutMs > 600000 silently clamping to 10min and still timing out; network relay path (shell relay for LAN URLs) adding latency.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/689c13cad640a0a6. Report an issue: GitHub.

Appendix: source

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

          JSON.stringify(payload),
          controller.signal
        );
      } else {
        response = await fetch(rpcUrl, {
          method: 'POST',
          headers,
          body: JSON.stringify(payload),
          signal: controller.signal,
        });
      }
    } catch (fetchErr) {
      if (controller.signal.aborted) {
        // Throw a fully-classified `CoreRpcError` here so the outer catch
        // doesn't re-wrap a bare `Error` and so callers can branch on
        // `err.kind === 'timeout'` (Sentry filter, soft toast skip). Use
        // the per-call `effectiveTimeoutMs` so the message reflects the
        // actual budget (#2156 raised the snapshot path to 90s).
        throw new CoreRpcError(
          `Core RPC ${payload.method} timed out after ${effectiveTimeoutMs}ms`,
          'timeout'
        );
      }
      throw fetchErr;
    } finally {
      clearTimeout(timeoutId);
    }

    if (!response.ok) {
      const text = await response.text();
      const httpMessage = `Core RPC HTTP ${response.status}: ${text || response.statusText}`;
      const kind = classifyRpcError(text || response.statusText, response.status);
      if (kind === 'auth_expired' && !suppressAuthExpiredEvent)
        dispatchAuthExpired(
          payload.method,
          classifyAuthExpiredReason(text || response.statusText, response.status)
        );

View on GitHub (pinned to 7491200858)