Stirling-Tools/Stirling-PDF · error · Error
Failed to save file
Error message
Failed to save file
What it means
Thrown by downloadFile() when request.localPath is set (a pre-chosen destination) and saveToLocalPath() returns success:false. The real reason is in result.error (filesystem write failure). This path is used when the caller already knows where to save and bypasses the save dialog.
Source
Thrown at frontend/editor/src/desktop/services/downloadService.ts:18
import type {
DownloadRequest,
DownloadResult,
} from "@core/services/downloadService";
import {
saveToLocalPath,
showSaveDialog,
} from "@app/services/localFileSaveService";
export type { DownloadRequest, DownloadResult };
export async function downloadFile(
request: DownloadRequest,
): Promise<DownloadResult> {
if (request.localPath) {
const result = await saveToLocalPath(request.data, request.localPath);
if (!result.success) {
throw new Error(result.error || "Failed to save file");
}
return { savedPath: request.localPath };
}
const savePath = await showSaveDialog(request.filename);
if (!savePath) {
return { cancelled: true };
}
const result = await saveToLocalPath(request.data, savePath);
if (!result.success) {
throw new Error(result.error || "Failed to save file");
}
return { savedPath: savePath };
}
export async function downloadFromUrl(View on GitHub (pinned to 9ef20dcab8)
Solutions
- Inspect result.error before it is wrapped — pre-check the path with exists()/permissions in the caller.
- Ensure request.localPath is within the Tauri fs plugin scope (configure allowed directories).
- Verify the parent directory exists and is writable before calling downloadFile.
- Fall back to the dialog-based flow (omit localPath) if the direct save fails.
Example fix
// before
await downloadFile({ data: blob, filename, localPath });
// after: ensure dir exists + scope, else fall back to dialog
const dir = await dirname(localPath);
if (!(await exists(dir))) await mkdir(dir, { recursive: true });
try { await downloadFile({ data: blob, filename, localPath }); }
catch (e) { await downloadFile({ data: blob, filename }); // dialog fallback } Defensive patterns
Strategy: validation
Validate before calling
// ensure parent dir exists and is in scope before the direct save
import { exists, mkdir } from '@tauri-apps/plugin-fs';
const dir = await dirname(request.localPath);
if (!(await exists(dir))) await mkdir(dir, { recursive: true }); Type guard
function isSaveFailure(e: unknown): e is Error {
return e instanceof Error && /Failed to save file/.test(e.message);
} Try / catch
try { await downloadFile({ data: blob, filename, localPath }); }
catch (e) {
if (isSaveFailure(e)) { await downloadFile({ data: blob, filename }); /* dialog fallback */ return; }
throw e;
} Prevention
- Verify the parent directory exists before saving.
- Keep localPath inside the Tauri fs plugin scope.
- Fall back to the save dialog when a direct path fails.
When it happens
Trigger: saveToLocalPath rejects the write for request.localPath: directory does not exist or is not writable, the file is locked by another process, the path is outside the allowed Tauri scope, or the disk is full.
Common situations: Caller passed an auto-generated path whose parent dir was deleted; path outside the Tauri fs allow-list (scope config); target file open in another app; read-only location.
Related errors
- Download failed (${response.status})
- File "${file.name}" not found in storage
- No valid files found in storage for ZIP download
- Sign up failed
- Failed to save authentication token
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/a117ce0fb7971576.
Report an issue: GitHub.