block/buzz · error · TauriInvokeError
TauriInvokeError
Error message
TauriInvokeError
What it means
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.
Source
Thrown at desktop/src/shared/api/tauri.ts:289
}
try {
return new TauriInvokeError(JSON.stringify(error), error);
} catch {
return new TauriInvokeError("Unknown Tauri error", error);
}
}
export async function invokeTauri<T>(
command: string,
args?: Record<string, unknown>,
): Promise<T> {
try {
return await tauriInvoke<T>(command, args);
} catch (error) {
// HTTP backoff lives in Rust. Do not apply its separate ApiCalls quota
// to the WebSocket gate, but preserve the failure for the caller.
throw toTauriError(error);
}
}
export function fromRawFeedItem(item: RawFeedItem) {
return {
id: item.id,
kind: item.kind,
pubkey: item.pubkey,
content: item.content,
createdAt: item.created_at,
channelId: item.channel_id,
channelName: item.channel_name,
// Canonicalize the wire `null` to undefined so FeedItem's optional
// channelType contract holds at runtime (enrichment and the DM
// notification filter both key off `=== undefined`).
channelType: item.channel_type ?? undefined,
tags: item.tags,
category: item.category,View on GitHub (pinned to 6c35e82bd5)
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.
Example fix
// before
await completeSend(channelId, content);
// after — guard for the missing bridge and normalize payload access
import { TauriInvokeError } from "@/shared/api/tauri";
if (!window.__TAURI_INTERNALS__) throw new Error("Tauri bridge unavailable; run in Tauri shell or E2E build");
try {
await completeSend(channelId, content);
} catch (err) {
const detail = err instanceof TauriInvokeError ? err.payload : err;
console.error("invoke failed:", detail);
throw err;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight guard: only call invokeTauri inside a Tauri webview
export function isTauriAvailable(): boolean {
return typeof window !== "undefined" &&
("__TAURI_INTERNALS__" in window || "__TAURI__" in window);
}
if (!isTauriAvailable()) {
throw new Error("invokeTauri called outside Tauri webview; build with the E2E/mock bridge or run the desktop shell");
} Type guard
import { TauriInvokeError } from "@/shared/api/tauri";
function isTauriInvokeError(err: unknown): err is TauriInvokeError {
return err instanceof TauriInvokeError;
} Try / catch
import { invokeTauri, TauriInvokeError } from "@/shared/api/tauri";
try {
const self = await invokeTauri<RelaySelf>("get_relay_self");
} catch (err) {
if (isTauriInvokeError(err)) {
// err.payload holds the raw backend rejection (string or object)
logger.error(`tauri command failed: ${err.message}`, err.payload);
if (/not found/i.test(err.message)) showSetupHint(); // missing command / bridge
else if (/network|relay|connect/i.test(err.message)) scheduleRetry();
else showGenericError(err.message);
} else {
throw err;
}
} Prevention
- 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.
When it happens
Trigger: 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)'.
Common situations: 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.
Related errors
- {error} (and the local stores could not be restored: {restor
- ${error}
- ${error.message}
- Media upload failed.
- Media fetch cancelled
AI-assisted analysis of block/buzz@6c35e82bd5 (2026-09-13).
Data as JSON: /api/errors/3daf7023d4abdb8a.
Report an issue: GitHub.