jamiepine/voicebox · error · Error
Failed to resolve save path from dialog
Error message
Failed to resolve save path from dialog
What it means
Thrown by the Tauri saveFile() platform implementation after the native save dialog resolves. The @tauri-apps/plugin-dialog save() can return a string path OR an object of shape { path: string } depending on dialog version/platform; the code handles both, then throws only if neither yields a usable string. This is a defensive guard for an unexpected return shape, not a normal user-cancel path (cancel returns null/falsy and is handled earlier).
Source
Thrown at tauri/src/platform/filesystem.ts:19
import type { FileFilter, PlatformFilesystem } from '@/platform/types';
export const tauriFilesystem: PlatformFilesystem = {
async saveFile(filename: string, blob: Blob, filters?: FileFilter[]) {
const { save } = await import('@tauri-apps/plugin-dialog');
const { writeFile } = await import('@tauri-apps/plugin-fs');
const filePath = await save({
defaultPath: filename,
filters: filters || [],
});
if (!filePath) return; // User cancelled the dialog
const resolvedPath =
typeof filePath === 'string' ? filePath : (filePath as { path: string }).path;
if (!resolvedPath) {
throw new Error('Failed to resolve save path from dialog');
}
const arrayBuffer = await blob.arrayBuffer();
await writeFile(resolvedPath, new Uint8Array(arrayBuffer));
},
async openPath(path: string) {
const { open } = await import('@tauri-apps/plugin-shell');
await open(path);
},
async pickDirectory(title: string) {
const { open } = await import('@tauri-apps/plugin-dialog');
const selected = await open({ directory: true, title });
if (!selected) return null;
const dir = typeof selected === 'string' ? selected : (selected as { path: string }).path;
return dir || null;
},View on GitHub (pinned to 51f49dea19)
Solutions
- Confirm @tauri-apps/plugin-dialog version matches the documented return shape (string on Tauri v1, { path } on some v2 paths).
- Log the raw resolved value when this throws to learn the actual shape: console.error('save returned', filePath).
- Broaden the normalization to also read filePath / name / path fields before giving up.
- Ensure the Tauri capability for dialog:allow-save is granted in capability files.
Example fix
// before
const resolvedPath =
typeof filePath === 'string' ? filePath : (filePath as { path: string }).path;
if (!resolvedPath) {
throw new Error('Failed to resolve save path from dialog');
}
// after
const resolvedPath =
typeof filePath === 'string'
? filePath
: (filePath as { path?: string; filePath?: string }).path ??
(filePath as { filePath?: string }).filePath;
if (!resolvedPath || typeof resolvedPath !== 'string') {
throw new Error(`Failed to resolve save path from dialog: ${JSON.stringify(filePath)}`);
} Defensive patterns
Strategy: type-guard
Type guard
type SaveDialogResult = string | { path: string } | { filePath: string } | null;
function resolveSavePath(v: SaveDialogResult): string | null {
if (typeof v === 'string') return v;
if (v && typeof v === 'object') return v.path ?? v.filePath ?? null;
return null;
} Try / catch
let resolvedPath: string | null = null;
try {
const filePath = await save({ defaultPath: filename, filters: filters || [] });
resolvedPath = resolveSavePath(filePath as SaveDialogResult);
} catch (e) {
throw new Error(`Save dialog failed: ${(e as Error).message}`);
}
if (!resolvedPath) return; // user cancelled or unsupported shape Prevention
- Pin @tauri-apps/plugin-dialog to a version whose return shape your normalize() expects.
- Grant dialog:allow-save in the Tauri capability file.
- Distinguish 'user cancelled' (null) from 'unexpected shape' in logging.
When it happens
Trigger: save() resolves to a truthy value that is neither a string nor an object with a `.path` string — e.g. a future/alternate dialog plugin returning { filePath: '...' } or a serialized File object. Practically rare with current @tauri-apps/plugin-dialog versions.
Common situations: Upgrading @tauri-apps/plugin-dialog across a major boundary that changes the return contract. A third-party dialog replacement. Misconfigured Tauri capabilities/permissions causing save() to resolve with an error object instead of throwing.
Related errors
- e instanceof Error ? e.message : errorMessage
- System audio capture is only available in the desktop app.
- Native audio device routing is only available in the desktop
- HTTP ${res.status}
- Navigator API is not available. This might be a Tauri config
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/da1a235d324c6d6c.
Report an issue: GitHub.