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
- Pass a per-call budget for known-slow RPCs: callCoreRpc({ method, params, timeoutMs: 90_000 }) — mirroring the app_state_snapshot fix in #2156
- Check whether the core process is alive and responsive (health endpoint / platform health RPC) and restart it if hung
- Branch on err.kind === 'timeout' to retry idempotent reads once instead of surfacing a hard error
- 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
- Pass explicit timeoutMs for known-slow RPCs (cold snapshots, migrations) instead of relying on the default
- Branch on err.kind === 'timeout' in telemetry filters so soft timeouts don't page anyone
- Watch core health: persistent timeouts usually mean a hung core, not a small budget
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.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Core RPC token unavailable in Tauri; local RPC auth cannot…
- [transport:cloud] timed out after ms
- [transport:lan] timed out after ms
- At least one of --expression, --tz, --command, or --name…
- [claude_agent_sdk] subprocess timed out while reading output
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)