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
- If isMissingKey is true, load the encryption key (Settings → encryption key, or api.loadKey) before retrying
- Verify the key id in metadata.json matches a key you possess; re-enter the original password
- 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
- 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
- Load the encryption key on every new device before syncing
- When changing the password, update it on all clients first
- Record the key password securely — lost keys make cloud files undecryptable
- Check metadata.json encryptKeyId against loaded keys before uploading
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
- missing-key
- Invalid font-family value for "${property}": function calls
- Invalid font src: only data: URIs are allowed in @font-face.
- Theme CSS contains forbidden at-rules (@import, @media, @key
- Invalid budget id "${id}". Check the id of your budget in th
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/30eaa4de87197727.
Report an issue: GitHub.