{"record":{"id":"4ba7b20a622d8963","repo":"tinyhumansai/openhuman","slug":"core-rpc-token-unavailable-in-tauri-local-rpc-aut","errorCode":null,"errorMessage":"Core RPC token unavailable in Tauri; local RPC auth cannot be satisfied","messagePattern":"Core RPC token unavailable in Tauri; local RPC auth cannot be satisfied","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/coreRpcClient.ts","lineNumber":679,"sourceCode":"  const effectiveTimeoutMs = resolvePerCallTimeoutMs(timeoutMs);\n  const payload: JsonRpcRequestBody = {\n    jsonrpc: '2.0',\n    id: nextJsonRpcId++,\n    method: normalizedMethod,\n    params: params ?? {},\n  };\n\n  try {\n    const [rpcUrl, token] = await Promise.all([getCoreRpcUrl(), getCoreRpcToken()]);\n    coreRpcLog('HTTP request', { id: payload.id, method: payload.method });\n    if (normalizedMethod === 'openhuman.auth_store_session') {\n      coreRpcLog('[rpc] auth_store_session routing', {\n        rpcUrl,\n        tokenSource: getStoredCoreToken() ? 'cloud-stored' : 'local-resolved',\n      });\n    }\n    if (isTauri() && !token) {\n      throw new Error('Core RPC token unavailable in Tauri; local RPC auth cannot be satisfied');\n    }\n\n    const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n    if (token) {\n      headers['Authorization'] = `Bearer ${token}`;\n    }\n    // Bound the fetch. Without this a hung core sidecar would block every\n    // caller (and the UI) forever. We use a manual AbortController +\n    // setTimeout rather than AbortSignal.timeout() so test fake timers can\n    // drive the abort deterministically. Per-call `timeoutMs` (clamped) lets\n    // legitimately-slow RPCs such as first-launch `app_state_snapshot`\n    // (#2156) opt into a longer-but-still-bounded budget.\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), effectiveTimeoutMs);\n    let response: Response;\n    try {\n      if (isTauri() && rpcUrlNeedsShellRelay(rpcUrl)) {\n        // Self-hosted runtime on a LAN IP — the secure `tauri://localhost`","sourceCodeStart":661,"sourceCodeEnd":697,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/coreRpcClient.ts#L661-L697","documentation":"Thrown by callCoreRpc when running inside Tauri and the bearer token could not be obtained: the shell mints a per-launch hex token for the embedded core (handed over via run_server_embedded_with_ready), and every local RPC must present it. When both the cloud-stored token and the Tauri core_rpc_token command yield nothing, the request cannot be authenticated and is refused before hitting the wire.","triggerScenarios":"An RPC fires before the core process is ready (the shell has not yet minted/exposed the token — e.g. a service or effect racing app boot), the core crashed at startup so the token command fails/returns nothing, or the Tauri IPC layer (CEF window.ipc.postMessage path) fails synchronously.","commonSituations":"First-launch races (early fetches like app_state_snapshot firing before BootCheckGate/CoreStateProvider complete); core crash-loop on boot (bad config, corrupted workspace); running the UI against a core that exited; CEF IPC quirks referenced by the Sentry TAURI-REACT issues in the source comment.","solutions":["Await the app's boot gates (CoreStateProvider / BootCheckGate) before issuing RPCs from new code — they exist precisely to sequence this","Restart the app; a fresh launch re-mints the token via start_core_process","Check the core actually booted: core logs / the health endpoint on the core port","Retry the call after a short delay — token availability is transient during boot","If developing with an external core, set OPENHUMAN_CORE_TOKEN or use the documented reuse env var"],"exampleFix":"// before\nconst snap = await callCoreRpc({ method: 'openhuman.app_state_snapshot' });\n\n// after\nawait coreReady; // gate from CoreStateProvider / boot promise\nconst snap = await callCoreRpc({ method: 'openhuman.app_state_snapshot' });","handlingStrategy":"retry","validationCode":"// Wait for boot before the first RPC instead of racing the token\nawait coreStateReady; // exposed by CoreStateProvider / BootCheckGate\nawait callCoreRpc({ method: 'openhuman.app_state_snapshot' });","typeGuard":null,"tryCatchPattern":"for (let attempt = 0; attempt < 5; attempt++) {\n  try {\n    return await callCoreRpc(payload);\n  } catch (e) {\n    if (e instanceof Error && e.message.includes('token unavailable')) {\n      await delay(500 * 2 ** attempt); // core still booting — token arrives shortly\n      continue;\n    }\n    throw e;\n  }\n}","preventionTips":["Sequence new RPCs behind the app's boot gates rather than firing on mount","After a core restart, clear the cached token before the next call (the restart path already does)","If the token never appears, check that the core process actually started (health endpoint) — retrying a dead core is pointless"],"tags":["rpc","auth","tauri","startup","race-condition"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}