tinyhumansai/openhuman · error

[transport:local] ${method} timed out after ${this.timeoutMs

Error message

[transport:local] ${method} timed out after ${this.timeoutMs}ms

What it means

Local transport timeout: the desktop renderer's `fetch` to the in-process core at `127.0.0.1:<port>/rpc` rejected with the shared controller aborted. Sources: the internal `setTimeout` (default 30s) or the caller's own `opts.signal` — the `controller.signal.aborted` check conflates them, so deliberate cancellation is also reported as a timeout. Since this is loopback, network latency is not the issue: the core process is busy, hung, restarting, or the method genuinely needs more than 30s.

Source

Thrown at app/src/services/transport/LocalTransport.ts:68

    }

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);

    // Merge caller signal with timeout signal.
    opts?.signal?.addEventListener('abort', () => controller.abort());

    let response: Response;
    try {
      response = await fetch(rpcUrl, {
        method: 'POST',
        headers,
        body: JSON.stringify(payload),
        signal: controller.signal,
      });
    } catch (err) {
      if (controller.signal.aborted) {
        throw new Error(`[transport:local] ${method} timed out after ${this.timeoutMs}ms`);
      }
      throw err;
    } finally {
      clearTimeout(timeoutId);
    }

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`[transport:local] HTTP ${response.status}: ${text || response.statusText}`);
    }

    const json = (await response.json()) as JsonRpcResponse<T>;

    if (json.error) {
      logErr('[transport:local] ← %s error: %s', method, json.error.message);
      throw new Error(json.error.message ?? 'Core RPC returned an error');
    }
    if (!Object.prototype.hasOwnProperty.call(json, 'result')) {

View on GitHub (pinned to a221052e0d)

Solutions

  1. Check the core log for the method's duration — if it completes in >30s, raise the transport's `timeoutMs` for that call or make the method async/ streamed core-side
  2. Confirm the core process is healthy (daemonHealthService / `openhuman ping`) and not restart-looping
  3. If you pass `opts.signal`, verify whether your own cancellation caused the abort before treating it as a hang
  4. Retry idempotent calls once the core is idle

Example fix

// before
new LocalTransport(getCoreRpcUrl, getCoreRpcToken) // 30s default
await transport.call('openhuman.memory_full_sync', {});

// after
new LocalTransport(getCoreRpcUrl, getCoreRpcToken, 120_000)
await transport.call('openhuman.memory_full_sync', {});
Defensive patterns

Strategy: retry

Validate before calling

import { daemonHealthService } from '../services/daemonHealthService';

if (!(await daemonHealthService.isHealthy())) {
  await restartCoreProcess(); // core down/restart-looping — retrying the call is pointless
}

Type guard

function isLocalTimeout(e: unknown): boolean {
  return e instanceof Error && e.message.startsWith('[transport:local]') && e.message.includes('timed out');
}

Try / catch

for (let a = 1; a <= 2; a++) {
  try { return await local.call(m, p); }
  catch (e) {
    if (!isLocalTimeout(e) || a === 2 || opts?.signal?.aborted) throw e;
    await sleep(2 ** a * 500);
  }
}

Prevention

When it happens

Trigger: Long-running RPC methods (memory sync, full-tool runs, backfills) exceeding 30s; the core blocked on a synchronous operation or deadlock; core mid-restart (CoreProcessHandle respawn) when the request lands; caller aborting via `opts.signal` and the message misattributing it.

Common situations: First-run heavy ingestion on a big workspace; a core panic loop during development; devtools network throttling accidentally applied to localhost; a new method added without considering the 30s ceiling.

Understand the failure class

Related errors


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