{"record":{"id":"a680c8dbf29979f2","repo":"actualbudget/actual","slug":"getsyncerror-result-error-reason-localbudget-id","errorCode":null,"errorMessage":"getSyncError(result.error.reason, localBudget.id, result.error.meta)","messagePattern":"getSyncError\\(result\\.error\\.reason, localBudget\\.id, result\\.error\\.meta\\)","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/api.ts","lineNumber":242,"sourceCode":"\n    const result = await handlers['key-test']({\n      cloudFileId: remoteBudget ? remoteBudget.fileId : localBudget.cloudFileId,\n      password,\n    });\n    if (result.error) {\n      throw withErrorCode(\n        new Error(getTestKeyError(result.error)),\n        result.error.reason,\n      );\n    }\n  }\n\n  // Sync the local budget file\n  if (localBudget) {\n    await handlers['load-budget']({ id: localBudget.id });\n    const result = await handlers['sync-budget']();\n    if (result.error) {\n      throw withErrorCode(\n        new Error(\n          getSyncError(result.error.reason, localBudget.id, result.error.meta),\n        ),\n        result.error.reason,\n      );\n    }\n    return;\n  }\n\n  // Download the remote file (no need to perform a sync as the file will already be up-to-date)\n  const result = await handlers['download-budget']({\n    cloudFileId: remoteBudget.fileId,\n  });\n  if (result.error) {\n    logger.log('Full error details', result.error);\n    throw withErrorCode(\n      new Error(getDownloadError(result.error)),\n      result.error.reason,","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/api.ts#L224-L260","documentation":"Thrown by api/download-budget in packages/loot-core/src/server/api.ts after the local budget is loaded and handlers['sync-budget']() returns an error. The reason plus meta are formatted by getSyncError() into messages for out-of-sync-migrations/data, invalid-schema (newer DB schema), budget-not-found, or clock-drift. The original reason code is preserved on the thrown error.","triggerScenarios":"downloadBudget(syncId) with an existing local budget whose initial sync fails: local data out of sync with migrations, schema newer than app, device clock drifted from the sync server, or the local file's group id no longer exists on the server.","commonSituations":"Upgraded Actual partially (old app, new budget); Docker/VM clock skew; budget restored from backup into a mismatched app version; server wiped while a stale local file remains.","solutions":["Update Actual to the latest version on all devices (schema/migration mismatches)","Sync system clock via NTP if reason is clock-drift","If out-of-sync-data cannot resolve, back up and delete the stale local file, then re-download","Read error.code for the precise reason and branch accordingly"],"exampleFix":"// before\nawait api.downloadBudget(syncId);\n// after\ntry {\n  await api.downloadBudget(syncId);\n} catch (e) {\n  if (e.code === 'clock-drift') { await syncClock(); await api.downloadBudget(syncId); }\n  else throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Clock sanity before syncing\nconst skewMin = Math.abs(Date.now() - Date.parse(await fetchServerTime())) / 60000;\nif (skewMin > 5) console.warn('Clock drift detected; sync may fail with clock-drift');","typeGuard":"function isSyncError(e: unknown): e is { code: string } {\n  return typeof e === 'object' && e !== null && typeof (e as any).code === 'string';\n}","tryCatchPattern":"try {\n  await api.downloadBudget(syncId);\n} catch (e) {\n  if (isSyncError(e) && e.code === 'clock-drift') { await syncNtp(); await api.downloadBudget(syncId); }\n  else if (isSyncError(e) && e.code === 'out-of-sync-data') await updateActualBeforeRetry();\n  else throw e;\n}","preventionTips":["Run NTP time sync on hosts, containers, and CI runners","Keep all Actual installations on the same version","Back up and remove stale local budget files that cannot sync","Check error.code before retrying — only clock-drift/network reasons are retry-safe"],"tags":["sync","clock-drift","schema"],"backgroundTag":"budget-sync-failed","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}