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

  1. Inspect result.error before it is wrapped — pre-check the path with exists()/permissions in the caller.
  2. Ensure request.localPath is within the Tauri fs plugin scope (configure allowed directories).
  3. Verify the parent directory exists and is writable before calling downloadFile.
  4. 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

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


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/a117ce0fb7971576. Report an issue: GitHub.