actualbudget/actual · error

key-make must be called with file loaded

Error message

key-make must be called with file loaded

What it means

keyMake lets a user create or change the budget's encryption key, but only when a budget file is loaded — prefs.getPrefs() returns null otherwise. Without a loaded file there is no place to store the key metadata (salt, id), so the call is rejected with 'key-make must be called with file loaded'.

Source

Thrown at packages/loot-core/src/server/encryption/app.ts:29

import * as encryption from '.';

export type EncryptionHandlers = {
  'key-make': typeof keyMake;
  'key-test': typeof keyTest;
};

export const app = createApp<EncryptionHandlers>();
app.method('key-make', keyMake);
app.method('key-test', keyTest);

// A user can only enable/change their key with the file loaded. This
// will change in the future: during onboarding the user should be
// able to enable encryption. (Imagine if they are importing data from
// another source, they should be able to encrypt first)
async function keyMake({ password }: { password: string }) {
  if (!prefs.getPrefs()) {
    throw new Error('key-make must be called with file loaded');
  }

  const salt = encryption.randomBytes(32).toString('base64');
  const id = uuidv4();
  const key = await encryption.createKey({ id, password, salt });

  // Load the key
  await encryption.loadKey(key);

  // Make some test data to use if the key is valid or not
  const testContent = await makeTestMessage(key.getId());

  // Changing your key necessitates a sync reset as well. This will
  // clear all existing encrypted data from the server so you won't
  // have a mix of data encrypted with different keys.
  return await resetSync({
    key,
    salt,

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Load a budget (loadBudget / createBudget) before calling key-make
  2. Defer encryption setup until after the file is opened in your app flow
  3. Check prefs.getPrefs() before invoking and show 'open a budget first' to the user
  4. Wait for the app's 'file loaded' / ready event before enabling key management calls

Example fix

// before
await send('key-make', { password });
// after
if (!prefs.getPrefs()) {
  await send('load-budget', { id: budgetId });
}
await send('key-make', { password });
Defensive patterns

Strategy: validation

Validate before calling

import { prefs } from '../server/prefs';
if (!prefs.getPrefs()) {
  throw new Error('Load a budget file before configuring encryption');
}

Type guard

function isFileLoaded(getPrefs: () => unknown): boolean {
  return getPrefs() != null;
}

Try / catch

try {
  await send('key-make', { password });
} catch (e) {
  if (e.message.includes('must be called with file loaded')) {
    await openBudgetFirst();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the key-make IPC/handler (keyMake({ password })) before any budget is opened — e.g. on a fresh install, after closing all budgets, or during onboarding before loadBudget finishes.

Common situations: Automation scripts invoking encryption setup at startup before a budget loads; testing the encryption API without loading a fixture budget; trying to pre-configure encryption before choosing/creating a file.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/b8931eae78de2d96. Report an issue: GitHub.