actualbudget/actual · error · FileUploadError

encrypt-failure

encrypt-failure

Error message

encrypt-failure

What it means

FileUploadError('encrypt-failure') is thrown by upload() when end-to-end encryption of the budget archive fails. The file has an encryptKeyId in prefs, and encryption.encrypt(zipContent, encryptKeyId) raised; the error carries isMissingKey: true when the cause was 'missing-key' (the encryption key is not loaded locally).

Source

Thrown at packages/loot-core/src/server/cloud-storage.ts:320

    id,
    groupId,
    budgetName,
    cloudFileId: originalCloudFileId,
    encryptKeyId,
  } = prefs.getPrefs();
  let cloudFileId = originalCloudFileId;
  let uploadContent = zipContent;
  let uploadMeta = null;

  // The upload process encrypts with the key tagged in the prefs for
  // the file. It will upload the file and the server is responsible
  // for checking that the key is up-to-date and rejecting it if not
  if (encryptKeyId) {
    let encrypted;
    try {
      encrypted = await encryption.encrypt(zipContent, encryptKeyId);
    } catch (e) {
      throw FileUploadError('encrypt-failure', {
        isMissingKey: e.message === 'missing-key',
      });
    }
    uploadContent = encrypted.value;
    uploadMeta = encrypted.meta;
  }

  if (!cloudFileId) {
    cloudFileId = uuidv4();
  }

  let res;
  try {
    res = await fetchJSON(getServer().SYNC_SERVER + '/upload-user-file', {
      method: 'POST',
      headers: {
        'Content-Length': String(uploadContent.length),
        'Content-Type': 'application/encrypted-file',

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. If isMissingKey is true, load the encryption key (Settings → encryption key, or api.loadKey) before retrying
  2. Verify the key id in metadata.json matches a key you possess; re-enter the original password
  3. If the key is truly lost, disable/recreate encryption: download without key is impossible, so restore from local backup and set a new key, then re-upload
  4. Check that cloudFileId/encryptKeyId prefs are consistent; clear stale prefs and re-link the cloud file

Example fix

// before
await uploadBudget();
// after
try {
  await uploadBudget();
} catch (e) {
  if (e.reason === 'encrypt-failure' && e.isMissingKey) {
    await loadKey(password);
    await uploadBudget();
  } else { throw e; }
}
Defensive patterns

Strategy: try-catch

Validate before calling

async function ensureKeyLoaded(encryptKeyId) {
  if (encryptKeyId && !encryption.hasKey(encryptKeyId)) {
    throw new Error(`encryption key ${encryptKeyId} not loaded; call loadKey(password) first`);
  }
}

Type guard

function canEncrypt(prefs, hasKeyFn = id => id != null) {
  return !prefs?.encryptKeyId || hasKeyFn(prefs.encryptKeyId);
}

Try / catch

try {
  await uploadBudget();
} catch (e) {
  if (e instanceof FileUploadError && e.reason === 'encrypt-failure') {
    if (e.isMissingKey) { await loadKey(keyPassword); return uploadBudget(); }
    throw new Error('encryption failed: check key/password', { cause: e });
  }
  throw e;
}

Prevention

When it happens

Trigger: uploading a budget that has end-to-end encryption enabled while the matching key is not loaded (missing-key), or the key/password is wrong or the crypto operation otherwise throws.

Common situations: restoring prefs/cloudFileId on a new device without re-entering the encryption key, changed encryption password on another device, corrupted key in the key store, or server URL switched so the stored keyId no longer matches any loaded key.

Related errors


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