{"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/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/services/transport/LocalTransport.ts#L50-L86","documentation":"LocalTransport arms a per-call AbortController with setTimeout(timeoutMs) (default 30_000ms, constructor third argument) and merges it with the caller's own opts.signal. If fetch rejects and controller.signal.aborted is true, it reports this timeout error. Note the guard checks only controller.signal.aborted, so a caller-initiated abort inside the window is also reported as a timeout.","triggerScenarios":"Any openhuman.* RPC whose server-side work exceeds timeoutMs: long agent turns, memory sync/embedding backfills, flow runs; a hung or paused core process; debugger breakpoints freezing the core; or the caller aborting its signal mid-call (conflated into the same message).","commonSituations":"The 30s default is too short for LLM-bound methods (chat turns, memory consolidation) on slow local models; core is under CPU load or swapping; core restarted while a request was in flight.","solutions":["Construct LocalTransport with a larger timeoutMs for known-slow operations (pass 120_000 as the third constructor arg)","Check core liveness first: GET /health on the same rpcUrl origin — if that also hangs, restart the core process rather than raising the timeout","For chat/agent calls, prefer a streaming method so no single call must finish within one window","If you pass opts.signal yourself, make sure you are not aborting it (the message cannot distinguish your abort from the timeout)"],"exampleFix":"// before\nconst t = new LocalTransport(getRpcUrl, getRpcToken);\n// after\nconst t = new LocalTransport(getRpcUrl, getRpcToken, 120_000);","handlingStrategy":"retry","validationCode":"// Pre-flight liveness so you only pay the long timeout for real work:\nasync function coreAlive(rpcUrl: string): Promise<boolean> {\n  const ctrl = new AbortController();\n  const t = setTimeout(() => ctrl.abort(), 2_000);\n  try {\n    const res = await fetch(rpcUrl, { method: 'GET', signal: ctrl.signal });\n    return res.ok || res.status === 404; // listener is up even on unknown GET routes\n  } catch { return false; } finally { clearTimeout(t); }\n}","typeGuard":"function isTimeoutError(err: unknown): boolean {\n  return err instanceof Error && err.message.includes('timed out after');\n}","tryCatchPattern":"try {\n  return await local.call(method, params);\n} catch (err) {\n  if (isTimeoutError(err)) {\n    await sleep(1_000);\n    return await local.call(method, params); // one retry; upgrade timeout if it repeats\n  }\n  throw err;\n}","preventionTips":["Size timeoutMs per call class: keep 30s for pings/gets, use 120s+ for agent/memory methods","Do not pass your own AbortSignal unless you handle its abort distinctly — the transport reports both as timeout","Pre-flight with a cheap ping when a stall is suspected instead of waiting out the full window"],"tags":["timeout","transport","json-rpc","abortcontroller","local-core"],"backgroundTag":"request-timeout","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}