actualbudget/actual · error
Unable to duplicate a budget that is not local.
Error message
Unable to duplicate a budget that is not local.
What it means
The `duplicateBudget` thunk in the desktop-client budgetfiles slice throws this when called without a local budget `id`. Duplication works by asking the backend to copy an existing local budget file, so a truthy `id` is a precondition. `DuplicateBudgetPayload.id` is optional (`id?: string`), so a caller that only supplies a `cloudId` (or forgets the id) reaches this guard. The guard exists because cloud-only budgets have no local file to duplicate.
Source
Thrown at packages/desktop-client/src/budgetfiles/budgetfilesSlice.ts:203
*/
cloudSync: boolean;
};
export const duplicateBudget = createAppAsyncThunk(
`${sliceName}/duplicateBudget`,
async (
{
id,
oldName,
newName,
managePage,
loadBudget = 'none',
cloudSync,
}: DuplicateBudgetPayload,
{ dispatch },
) => {
if (!id) {
throw new Error('Unable to duplicate a budget that is not local.');
}
try {
dispatch(
setAppState({
loadingText: t('Duplicating: {{oldName}} to: {{newName}}', {
oldName,
newName,
}),
}),
);
await send('duplicate-budget', {
id,
newName,
cloudSync,
open: loadBudget,
});View on GitHub (pinned to d4334cb6e6)
Solutions
- Ensure the payload includes the local budget's `id` (the local file id from `loadAllFiles`/budget metadata), not just `cloudId`.
- Only offer the Duplicate action for budgets that are downloaded locally; hide or disable it for cloud-only entries.
- If the budget is remote-only, first download it (downloadBudget with its cloudFileId) and then duplicate using the returned local id.
- Check for stale file metadata — refresh the file list so the local `id` is populated before dispatching.
Example fix
// before
await dispatch(duplicateBudget({ cloudId: file.cloudFileId, oldName: file.name, newName, cloudSync: true }));
// after
if (!file.id) {
alert('Download the budget before duplicating it.');
return;
}
await dispatch(duplicateBudget({ id: file.id, oldName: file.name, newName, cloudSync: true })); Defensive patterns
Strategy: validation
Validate before calling
const canDuplicate = (file?: { id?: string; cloudFileId?: string }) =>
typeof file?.id === 'string' && file.id.length > 0;
if (!canDuplicate(file)) {
alert('This budget is not downloaded locally and cannot be duplicated.');
return;
}
await dispatch(duplicateBudget({ id: file.id, ... })); Type guard
function hasLocalId(
file: { id?: string | undefined },
): file is { id: string } {
return typeof file.id === 'string' && file.id.length > 0;
} Try / catch
try {
await dispatch(duplicateBudget({ id, oldName, newName, cloudSync })).unwrap();
} catch (e) {
if (e instanceof Error && e.message.includes('not local')) {
alert('Download this budget first before duplicating it.');
} else {
throw e;
}
} Prevention
- Only render the Duplicate action for budgets with a local id (hasLocalId guard).
- Treat cloudId-only files as remote: route them through downloadBudget first.
- Refresh the file list (loadAllFiles) before building action payloads so ids are current.
- When writing integrations, make `id` required in your own wrapper type instead of relying on the optional payload type.
When it happens
Trigger: Calling `dispatch(duplicateBudget({...}))` where the payload's `id` field is undefined, null, or an empty string — e.g. dispatching from the budget manager for a file row that only has a `cloudId` (remote-only budget not yet downloaded), or a caller constructing the payload without `id`.
Common situations: User clicks 'Duplicate' on a budget in the manager page that exists only in the cloud (never downloaded locally); a UI regression drops the id from the payload; a custom integration/script reuses the thunk with only cloud metadata.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- Error importing budget: ${result.error}
- Error importing budget: no budget was loaded
- Error exporting budget: ${result.error}
- Error exporting budget: no data was returned
- No fields to update. Use --name to specify a new name.
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/51a8fa4fbbb1e848.
Report an issue: GitHub.