{"record":{"id":"5f96e4bb297fb5e4","repo":"tinyhumansai/openhuman","slug":"transport-local-method-timed-out-after-this","errorCode":null,"errorMessage":"[transport:local] ${method} timed out after ${this.timeoutMs}ms","messagePattern":"\\[transport:local\\] (.+?) timed out after (.+?)ms","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/transport/LocalTransport.ts","lineNumber":68,"sourceCode":"    }\n\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n\n    // Merge caller signal with timeout signal.\n    opts?.signal?.addEventListener('abort', () => controller.abort());\n\n    let response: Response;\n    try {\n      response = await fetch(rpcUrl, {\n        method: 'POST',\n        headers,\n        body: JSON.stringify(payload),\n        signal: controller.signal,\n      });\n    } catch (err) {\n      if (controller.signal.aborted) {\n        throw new Error(`[transport:local] ${method} timed out after ${this.timeoutMs}ms`);\n      }\n      throw err;\n    } finally {\n      clearTimeout(timeoutId);\n    }\n\n    if (!response.ok) {\n      const text = await response.text();\n      throw new Error(`[transport:local] HTTP ${response.status}: ${text || response.statusText}`);\n    }\n\n    const json = (await response.json()) as JsonRpcResponse<T>;\n\n    if (json.error) {\n      logErr('[transport:local] ← %s error: %s', method, json.error.message);\n      throw new Error(json.error.message ?? 'Core RPC returned an error');\n    }\n    if (!Object.prototype.hasOwnProperty.call(json, 'result')) {","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/transport/LocalTransport.ts#L50-L86","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","Confirm the core process is healthy (daemonHealthService / `openhuman ping`) and not restart-looping","If you pass `opts.signal`, verify whether your own cancellation caused the abort before treating it as a hang","Retry idempotent calls once the core is idle"],"exampleFix":"// before\nnew LocalTransport(getCoreRpcUrl, getCoreRpcToken) // 30s default\nawait transport.call('openhuman.memory_full_sync', {});\n\n// after\nnew LocalTransport(getCoreRpcUrl, getCoreRpcToken, 120_000)\nawait transport.call('openhuman.memory_full_sync', {});","handlingStrategy":"retry","validationCode":"import { daemonHealthService } from '../services/daemonHealthService';\n\nif (!(await daemonHealthService.isHealthy())) {\n  await restartCoreProcess(); // core down/restart-looping — retrying the call is pointless\n}","typeGuard":"function isLocalTimeout(e: unknown): boolean {\n  return e instanceof Error && e.message.startsWith('[transport:local]') && e.message.includes('timed out');\n}","tryCatchPattern":"for (let a = 1; a <= 2; a++) {\n  try { return await local.call(m, p); }\n  catch (e) {\n    if (!isLocalTimeout(e) || a === 2 || opts?.signal?.aborted) throw e;\n    await sleep(2 ** a * 500);\n  }\n}","preventionTips":["Give known-slow methods (sync, backfill, generation) a transport with a raised timeoutMs","Check core health before retrying — a hung/restart-looping core will time out every time","If you pass opts.signal, note that your own abort produces the same 'timed out' message; check opts.signal.aborted in the handler"],"tags":["timeout","core-rpc","transport","abort","loopback"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}