{"record":{"id":"b8931eae78de2d96","repo":"actualbudget/actual","slug":"key-make-must-be-called-with-file-loaded","errorCode":null,"errorMessage":"key-make must be called with file loaded","messagePattern":"key-make must be called with file loaded","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/encryption/app.ts","lineNumber":29,"sourceCode":"\nimport * as encryption from '.';\n\nexport type EncryptionHandlers = {\n  'key-make': typeof keyMake;\n  'key-test': typeof keyTest;\n};\n\nexport const app = createApp<EncryptionHandlers>();\napp.method('key-make', keyMake);\napp.method('key-test', keyTest);\n\n// A user can only enable/change their key with the file loaded. This\n// will change in the future: during onboarding the user should be\n// able to enable encryption. (Imagine if they are importing data from\n// another source, they should be able to encrypt first)\nasync function keyMake({ password }: { password: string }) {\n  if (!prefs.getPrefs()) {\n    throw new Error('key-make must be called with file loaded');\n  }\n\n  const salt = encryption.randomBytes(32).toString('base64');\n  const id = uuidv4();\n  const key = await encryption.createKey({ id, password, salt });\n\n  // Load the key\n  await encryption.loadKey(key);\n\n  // Make some test data to use if the key is valid or not\n  const testContent = await makeTestMessage(key.getId());\n\n  // Changing your key necessitates a sync reset as well. This will\n  // clear all existing encrypted data from the server so you won't\n  // have a mix of data encrypted with different keys.\n  return await resetSync({\n    key,\n    salt,","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/encryption/app.ts#L11-L47","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Load a budget (loadBudget / createBudget) before calling key-make","Defer encryption setup until after the file is opened in your app flow","Check prefs.getPrefs() before invoking and show 'open a budget first' to the user","Wait for the app's 'file loaded' / ready event before enabling key management calls"],"exampleFix":"// before\nawait send('key-make', { password });\n// after\nif (!prefs.getPrefs()) {\n  await send('load-budget', { id: budgetId });\n}\nawait send('key-make', { password });","handlingStrategy":"validation","validationCode":"import { prefs } from '../server/prefs';\nif (!prefs.getPrefs()) {\n  throw new Error('Load a budget file before configuring encryption');\n}","typeGuard":"function isFileLoaded(getPrefs: () => unknown): boolean {\n  return getPrefs() != null;\n}","tryCatchPattern":"try {\n  await send('key-make', { password });\n} catch (e) {\n  if (e.message.includes('must be called with file loaded')) {\n    await openBudgetFirst();\n  } else throw e;\n}","preventionTips":["Gate all key-management calls behind a 'budget loaded' state in your app flow","Listen for the file-loaded/ready event before enabling encryption UI","In scripts, call loadBudget/createBudget before key-make","Never assume prefs exist on a fresh install"],"tags":["encryption","precondition","state","budget-file"],"backgroundTag":"file-not-loaded","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}