tinyhumansai/openhuman · error · CoreRpcError

Core RPC ${payload.method} timed out after ${effectiveTimeou

Error message

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

What it means

A CoreRpcError with kind 'timeout' thrown by callCoreRpc: every RPC is bounded by an AbortController + setTimeout (manually wired so fake timers work in tests), with a per-call timeoutMs clamped into effectiveTimeoutMs. When the fetch aborts before the core answers, the abort is detected in the catch and re-thrown as this classified error so callers and Sentry filters can branch on err.kind === 'timeout'.

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 a221052e0d)

Solutions

  1. For known-slow methods, pass a larger per-call budget: callCoreRpc({ method, params, timeoutMs: 90_000 })
  2. Retry once — transient core congestion often clears; treat kind === 'timeout' as retryable (the analytics module already special-cases it)
  3. Restart the core if it is consistently timing out (possible deadlock) and check core CPU / logs around the timeouts
  4. If you are adding a legitimately slow RPC, budget its timeoutMs like #2156 did rather than letting it hit the default

Example fix

// before
await callCoreRpc({ method: 'openhuman.app_state_snapshot' });

// after
await callCoreRpc({ method: 'openhuman.app_state_snapshot', timeoutMs: 90_000 });
Defensive patterns

Strategy: retry

Validate before calling

// Budget slow methods explicitly instead of relying on the default
await callCoreRpc({ method: 'openhuman.app_state_snapshot', timeoutMs: 90_000 });

Type guard

import { CoreRpcError } from '../services/coreRpcClient';
function isRpcTimeout(e: unknown): e is CoreRpcError {
  return e instanceof CoreRpcError && e.kind === 'timeout';
}

Try / catch

try {
  return await callCoreRpc(payload);
} catch (e) {
  if (isRpcTimeout(e)) {
    await delay(1000);
    return callCoreRpc(payload); // one bounded retry; report if it repeats
  }
  throw e;
}

Prevention

When it happens

Trigger: The core is slow or hung: first-launch app_state_snapshot on a cold workspace (this path was explicitly raised to 90s in #2156), long config migrations at boot, a deadlocked or CPU-pinned core, or a genuinely slow method called with the default budget.

Common situations: Cold start with a large workspace/migration; core stuck on a lock (store contention); heavy operations (memory indexing) starving the RPC thread pool; slower hardware stretching slow calls past the default budget.

Understand the failure class

Related errors


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