{"record":{"id":"c78c726c038b2c03","repo":"actualbudget/actual","slug":"out-of-sync","errorCode":"out-of-sync","errorMessage":"out-of-sync","messagePattern":"out-of-sync","errorType":"error_code","errorClass":"SyncError","httpStatus":null,"severity":"critical","filePath":"packages/loot-core/src/server/sync/index.ts","lineNumber":816,"sourceCode":"        'server hash:',\n        res.merkle.hash,\n        'localTimeChanged:',\n        localTimeChanged,\n      );\n\n      if (rebuiltMerkle.trie.hash === res.merkle.hash) {\n        // Rebuilding the merkle worked... but why?\n        const clocks = await db.all<db.DbClockMessage>(\n          'SELECT * FROM messages_clock',\n        );\n        if (clocks.length !== 1) {\n          logger.log('Bad number of clocks:', clocks.length);\n        }\n        const hash = deserializeClock(clocks[0].clock).merkle.hash;\n        logger.log('Merkle hash in db:', hash);\n      }\n\n      throw new SyncError('out-of-sync');\n    }\n\n    receivedMessages = receivedMessages.concat(\n      await _fullSync(\n        new Timestamp(diffTime, 0, '0').toString(),\n        // If something local changed while we were syncing, always\n        // reset, token the counter. We never want to think syncing failed\n        // because we tried to syncing many times and couldn't sync,\n        // but it was because the user kept changing stuff in the\n        // middle of syncing.\n        localTimeChanged ? 0 : count + 1,\n        diffTime,\n      ),\n    );\n  } else {\n    // All synced up, store the current time as a simple optimization for the next sync\n    const requiresUpdate =\n      getClock().timestamp.toString() !== lastSyncedTimestamp;","sourceCodeStart":798,"sourceCodeEnd":834,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/sync/index.ts#L798-L834","documentation":"A SyncError thrown by _fullSync() after too many sync round-trips (10 rounds with no change in diffTime, or 100 rounds total) still cannot reconcile the local Merkle tree with the server's. The client logs both trees and gives up with 'out-of-sync', meaning local and server message histories have diverged in a way normal incremental syncing cannot repair.","triggerScenarios":"fullSync loops: local merkle hash != server merkle hash after receiving messages, diffTime stops advancing, count exceeds the limit → SyncError('out-of-sync').","commonSituations":"Server database was restored from backup or its messages were pruned/manually modified; a client force-synced with corrupted local clock/merkle state; very old budgets where clock history diverged; duplicate budgets pointing at the same group id after a bad restore.","solutions":["Back up the budget, then use the 'repair' / reset sync data option so the server group and merkle tree are rebuilt from the local data","Verify the sync server was not restored from an older backup; restore matching backups if it was","Clear the local budget db and re-download the budget fresh from the server","Check sync-server logs/messages table for manual tampering or pruning and stop doing manual db edits"],"exampleFix":"// before: persistent out-of-sync\nawait fullSync(); // out-of-sync\n// after: reset sync data on the authoritative client\n// File → 'Reset sync data' / repair endpoint, then let other devices re-download","handlingStrategy":"retry","validationCode":"const localHash = getClock().merkle.hash;\nconst serverHash = await fetchServerMerkleHash(groupId);\nif (localHash !== serverHash) {\n  logger.warn('Merkle hashes differ; may need sync repair');\n}","typeGuard":"function isOutOfSync(e: unknown): e is SyncError {\n  return e instanceof SyncError && e.reason?.code === 'out-of-sync';\n}","tryCatchPattern":"try {\n  await fullSync();\n} catch (e) {\n  if (isOutOfSync(e)) {\n    await resetSyncData(); // repair: rebuild server group from local\n  } else throw e;\n}","preventionTips":["Never restore the sync server from an older backup without also resetting client sync data","Avoid manual edits to the server messages table","Take backups before major sync-server maintenance","Rebuild merkle via the repair tool at first symptom rather than repeated retries"],"tags":["sync","merkle","consistency"],"backgroundTag":"sync-out-of-sync","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}