actualbudget/actual · error
zipMeta ? getUnsafeZipError(zipMeta) : error
Error message
zipMeta ? getUnsafeZipError(zipMeta) : error
What it means
`importBudget` sends the file path to the backend via `import-budget`; if the backend returns an `error`, the thunk throws it as a JS Error. Before throwing it checks `getUnsafeZipMeta(meta)` — when the failure is a flagged unsafe zip archive (e.g. zip-slip path traversal or unsafe compression ratio), the error is replaced with the more specific `getUnsafeZipError(zipMeta)` message. Otherwise the raw backend error string is thrown.
Source
Thrown at packages/desktop-client/src/budgetfiles/budgetfilesSlice.ts:250
: new Error('Error duplicating budget: ' + String(error));
} finally {
dispatch(setAppState({ loadingText: null }));
}
},
);
type ImportBudgetPayload = {
filepath: string;
type: Parameters<Handlers['import-budget']>[0]['type'];
};
export const importBudget = createAppAsyncThunk(
`${sliceName}/importBudget`,
async ({ filepath, type }: ImportBudgetPayload, { dispatch }) => {
const { error, meta } = await send('import-budget', { filepath, type });
if (error) {
const zipMeta = getUnsafeZipMeta(meta);
throw new Error(zipMeta ? getUnsafeZipError(zipMeta) : error);
}
dispatch(closeModal());
await dispatch(loadPrefs());
},
);
type UploadBudgetPayload = {
id?: string;
};
export const uploadBudget = createAppAsyncThunk(
`${sliceName}/uploadBudget`,
async ({ id }: UploadBudgetPayload, { dispatch }) => {
const { error } = await send('upload-budget', { id });
if (error) {
return { error };
}View on GitHub (pinned to d4334cb6e6)
Solutions
- Read the thrown message: if it is the unsafe-zip error, re-export the budget from the source application and avoid untrusted/hand-edited zip files.
- Verify the file path exists and is readable before importing; on desktop re-pick the file via the file dialog.
- Confirm the file type matches the `type` argument (e.g. 'ynab4', 'ynab5', 'actual') — importing with the wrong type fails validation.
- Try importing an uncompressed/known-good export to isolate whether the archive itself is the problem.
Example fix
// before
await dispatch(importBudget({ filepath: '/downloads/budget.zip', type: 'actual' }));
// after
const stat = await window.fs.stat(filepath); // or fs.existsSync in node context
if (!stat) { alert('File not found: ' + filepath); return; }
try {
await dispatch(importBudget({ filepath, type: 'actual' })).unwrap();
} catch (e) {
alert('Import failed: ' + (e instanceof Error ? e.message : String(e)));
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!filepath || !(await fileExists(filepath))) {
alert('Import file not found: ' + filepath);
return;
}
const lower = filepath.toLowerCase();
if (!['.zip', '.ynab', '.json', '.csv', '.ofx', '.qfx'].some(ext => lower.endsWith(ext))) {
alert('Unsupported import file type.');
return;
} Type guard
function isImportFailure(
e: unknown,
): e is Error & { message: string } {
return e instanceof Error &&
(e.message.includes('zip') || e.message.includes('import'));
} Try / catch
try {
await dispatch(importBudget({ filepath, type })).unwrap();
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (/zip/i.test(msg)) {
alert('The archive is unsafe or corrupted. Re-export the budget and try again.');
} else {
alert('Import failed: ' + msg);
}
} Prevention
- Only import archives exported by a trusted application (Actual export, YNAB export).
- Validate the file exists and the extension matches the declared `type` before dispatching.
- Never hand-edit zip archives; re-export from source after data changes.
- Keep the client updated so zip-slip/bomb detection rules match current security expectations.
When it happens
Trigger: Calling `dispatch(importBudget({ filepath, type }))` when the backend import fails: the file at `filepath` is not a valid/recognizable budget or export format, the file is a malicious/unsafe zip archive (detected via zip metadata in `meta`), the path is unreadable, or the imported data fails backend validation — any case where `send('import-budget')` resolves with a truthy `error`.
Common situations: Importing a corrupted or truncated .zip budget export; importing a random zip that isn't an Actual export; importing YNAB4/YNAB5/Actual files with schema problems; on desktop, a file path that no longer exists or lacks read permission.
Related errors
- Error importing budget: ${result.error}
- Error importing budget: no budget was loaded
- Could not find file: ${path}
- File name matches multiple files: ${path}
- Error reading zip file
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/db841a5048c8a9d1.
Report an issue: GitHub.