{"record":{"id":"3daf7023d4abdb8a","repo":"block/buzz","slug":"tauriinvokeerror","errorCode":null,"errorMessage":"TauriInvokeError","messagePattern":"TauriInvokeError","errorType":"exception","errorClass":"TauriInvokeError","httpStatus":null,"severity":"error","filePath":"desktop/src/shared/api/tauri.ts","lineNumber":289,"sourceCode":"  }\n\n  try {\n    return new TauriInvokeError(JSON.stringify(error), error);\n  } catch {\n    return new TauriInvokeError(\"Unknown Tauri error\", error);\n  }\n}\n\nexport async function invokeTauri<T>(\n  command: string,\n  args?: Record<string, unknown>,\n): Promise<T> {\n  try {\n    return await tauriInvoke<T>(command, args);\n  } catch (error) {\n    // HTTP backoff lives in Rust. Do not apply its separate ApiCalls quota\n    // to the WebSocket gate, but preserve the failure for the caller.\n    throw toTauriError(error);\n  }\n}\n\nexport function fromRawFeedItem(item: RawFeedItem) {\n  return {\n    id: item.id,\n    kind: item.kind,\n    pubkey: item.pubkey,\n    content: item.content,\n    createdAt: item.created_at,\n    channelId: item.channel_id,\n    channelName: item.channel_name,\n    // Canonicalize the wire `null` to undefined so FeedItem's optional\n    // channelType contract holds at runtime (enrichment and the DM\n    // notification filter both key off `=== undefined`).\n    channelType: item.channel_type ?? undefined,\n    tags: item.tags,\n    category: item.category,","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/block/buzz/blob/6c35e82bd50f4ad6587554eeb429e7378d474ba7/desktop/src/shared/api/tauri.ts#L271-L307","documentation":"invokeTauri (desktop/src/shared/api/tauri.ts:280-291) wraps Tauri's invoke() IPC bridge between the React frontend and the Rust backend. When the invocation rejects, toTauriError normalizes whatever the backend rejected with — a plain string, a {message: string} object, or an arbitrary serialized payload — into a TauriInvokeError (an Error subclass carrying the raw payload), preserving the failure for the caller. The error itself originates in Rust: a failed command, an anyhow/Result::Err serialization, a panic message, or the webview failing to reach the Tauri IPC at all, so its message text varies and this wrapper is the single normalization seam every caller funnels through.","triggerScenarios":"Any awaited call to invokeTauri rejects — the callers listed hit it via commands like completeSend, getRelaySelf, fetchPersonaCatalogPublications, fetchTeamCatalogPublications, useAddChannelMembersMutation, and useAttachManagedAgentToChannelMutation. Concretely: the Rust command returns Err (invalid args, relay unreachable, auth failure, DB error), a command panics, arg/result JSON serialization fails, or the code runs outside the Tauri webview (plain browser / E2E build without the Tauri bridge) so tauriInvoke itself throws 'Cannot read properties of undefined (reading invoke)'.","commonSituations":"The desktop frontend runs in a plain Vite dev server or an E2E build missing the mock Tauri bridge (pnpm build instead of pnpm build:e2e), so every invoke fails; the Rust backend command errors because the relay WebSocket is down or auth expired; an args object field name/type mismatches the Rust command signature so deserialization fails; or a new command name is invoked that the backend does not register.","solutions":["Read error.message and error.payload (the TauriInvokeError carries the raw backend payload) to find the underlying Rust error; fix the root cause on the backend or in the args passed to invokeTauri.","If running outside the Tauri app (plain browser dev or Playwright), rebuild with the E2E/mock bridge — use pnpm build:e2e or the Tauri dev shell (just dev / just desktop-dev) — never a plain pnpm run build.","Verify each key in the args object matches the Rust command's #[tauri::command] parameter names and types (snake_case on the Rust side, camelCase is auto-converted only per Tauri version — check the command signature).","Confirm the command name string matches a registered invoke_handler command in desktop/src-tauri (typos in the command string reject with 'command not found').","For relay/auth-related failures surfaced through this error, check the relay is running and credentials (BUZZ_RELAY_URL / key configured in the backend) are valid, then retry."],"exampleFix":"// before\nawait completeSend(channelId, content);\n\n// after — guard for the missing bridge and normalize payload access\nimport { TauriInvokeError } from \"@/shared/api/tauri\";\nif (!window.__TAURI_INTERNALS__) throw new Error(\"Tauri bridge unavailable; run in Tauri shell or E2E build\");\ntry {\n  await completeSend(channelId, content);\n} catch (err) {\n  const detail = err instanceof TauriInvokeError ? err.payload : err;\n  console.error(\"invoke failed:\", detail);\n  throw err;\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight guard: only call invokeTauri inside a Tauri webview\nexport function isTauriAvailable(): boolean {\n  return typeof window !== \"undefined\" &&\n    (\"__TAURI_INTERNALS__\" in window || \"__TAURI__\" in window);\n}\nif (!isTauriAvailable()) {\n  throw new Error(\"invokeTauri called outside Tauri webview; build with the E2E/mock bridge or run the desktop shell\");\n}","typeGuard":"import { TauriInvokeError } from \"@/shared/api/tauri\";\nfunction isTauriInvokeError(err: unknown): err is TauriInvokeError {\n  return err instanceof TauriInvokeError;\n}","tryCatchPattern":"import { invokeTauri, TauriInvokeError } from \"@/shared/api/tauri\";\ntry {\n  const self = await invokeTauri<RelaySelf>(\"get_relay_self\");\n} catch (err) {\n  if (isTauriInvokeError(err)) {\n    // err.payload holds the raw backend rejection (string or object)\n    logger.error(`tauri command failed: ${err.message}`, err.payload);\n    if (/not found/i.test(err.message)) showSetupHint(); // missing command / bridge\n    else if (/network|relay|connect/i.test(err.message)) scheduleRetry();\n    else showGenericError(err.message);\n  } else {\n    throw err;\n  }\n}","preventionTips":["Always invoke through invokeTauri, never tauriInvoke directly, so rejections are normalized to TauriInvokeError with the payload preserved.","In E2E or browser dev, ensure the mock bridge is compiled in — build with pnpm build:e2e (pnpm build:e2e), never plain pnpm run build, or every invoke fails identically.","Keep Rust command parameter names/types and the TS args object in sync; mismatched args fail at deserialization on every call.","Check window.__TAURI_INTERNALS__ before invoking when code may run in a non-Tauri context, and surface a clear 'not running in desktop shell' error.","Log err.payload, not just err.message, when triaging — the backend's real error (relay down, auth, DB) lives there."],"tags":["tauri","ipc","typescript","frontend","backend-error"],"backgroundTag":"api-error-response","analyzedSha":"6c35e82bd50f4ad6587554eeb429e7378d474ba7","analyzedAt":"2026-09-13T09:13:27.080Z","contentChangedAt":"2026-09-13T09:13:27.080Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}