{"record":{"id":"2b570f0c0d7fc6cc","repo":"actualbudget/actual","slug":"encrypt-failure","errorCode":"encrypt-failure","errorMessage":"encrypt-failure","messagePattern":"encrypt-failure","errorType":"error_code","errorClass":"SyncError","httpStatus":null,"severity":"critical","filePath":"packages/loot-core/src/server/sync/encoder.ts","lineNumber":64,"sourceCode":"  for (const msg of messages) {\n    const binaryMsg = toBinary(\n      MessageSchema,\n      create(MessageSchema, {\n        dataset: msg.dataset,\n        row: msg.row,\n        column: msg.column,\n        value: msg.value as string,\n      }),\n    );\n\n    let content: Uint8Array;\n    let isEncrypted: boolean;\n    if (encryptKeyId) {\n      let result;\n      try {\n        result = await encryption.encrypt(binaryMsg, encryptKeyId);\n      } catch (e) {\n        throw new SyncError('encrypt-failure', {\n          isMissingKey: e.message === 'missing-key',\n        });\n      }\n\n      content = toBinary(\n        EncryptedDataSchema,\n        create(EncryptedDataSchema, {\n          data: result.value,\n          iv: Buffer.from(result.meta.iv, 'base64'),\n          authTag: Buffer.from(result.meta.authTag, 'base64'),\n        }),\n      );\n      isEncrypted = true;\n    } else {\n      content = binaryMsg;\n      isEncrypted = false;\n    }\n","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/sync/encoder.ts#L46-L82","documentation":"A SyncError thrown by the sync encoder (encode) when encryption.encrypt() throws while encrypting an outgoing sync message with the budget's encryption key. The error carries isMissingKey=true when the underlying failure was specifically a missing key. It means the client could not produce an encrypted payload, so the sync request is aborted before it is sent.","triggerScenarios":"encode() is called with a non-empty encryptKeyId (end-to-end encryption enabled on the budget) and encryption.encrypt(binaryMsg, encryptKeyId) throws — most commonly because the key for encryptKeyId is not loaded locally ('missing-key'), or the key data is corrupt/unavailable.","commonSituations":"Restoring a budget or syncing an encrypted budget on a device where the encryption key was never entered or was lost; switching the budget's encryption key on the server without updating the client; a fresh client install whose local key store does not yet have the key id referenced by budget prefs.","solutions":["Re-enter/re-download the budget encryption key on this client (Settings → encryption key) so 'missing-key' is resolved","Verify the budget's encryptKeyId preference matches an existing key id in the server key store","If end-to-end encryption is not intended, disable encryption for the budget so encode() runs without encryptKeyId","Rebuild the sync data from a known-good encrypted backup if the key is unrecoverable"],"exampleFix":"// before: client missing key\nawait sync(); // SyncError('encrypt-failure', { isMissingKey: true })\n// after: load the key first\nimport * as encryption from './encryption';\nawait encryption.loadKey(base64Key);\nawait sync(); // succeeds","handlingStrategy":"try-catch","validationCode":"const { encryptKeyId } = prefs.getPrefs();\nif (encryptKeyId && !(await encryption.getKey(encryptKeyId))) {\n  throw new Error('Encryption key missing before sync');\n}","typeGuard":"function isEncryptFailure(e: unknown): e is SyncError & { reason: { isMissingKey: boolean } } {\n  return e instanceof SyncError && e.reason?.code === 'encrypt-failure';\n}","tryCatchPattern":"try {\n  await fullSync();\n} catch (e) {\n  if (isEncryptFailure(e)) {\n    if (e.reason.isMissingKey) await promptForEncryptionKey();\n    else throw e;\n  } else throw e;\n}","preventionTips":["Enter the budget encryption key on every device before enabling sync","Keep a safe record of the key id and key material","Disable e2ee on budgets that don't require it","Test sync after key rotation on all devices"],"tags":["sync","encryption","e2ee"],"backgroundTag":"missing-encryption-key","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}