{"record":{"id":"34156491e201fa84","repo":"actualbudget/actual","slug":"timestamp-clockdrifterror","errorCode":null,"errorMessage":"Timestamp.ClockDriftError","messagePattern":"Timestamp\\.ClockDriftError","errorType":"exception","errorClass":"Timestamp.ClockDriftError","httpStatus":null,"severity":"critical","filePath":"packages/crdt/src/crdt/timestamp.ts","lineNumber":215,"sourceCode":"      return null;\n    }\n\n    // retrieve the local wall time\n    const phys = Date.now();\n\n    // unpack the clock.timestamp logical time and counter\n    const lOld = clock.timestamp.millis();\n    const cOld = clock.timestamp.counter();\n\n    // calculate the next logical time and counter\n    // * ensure that the logical time never goes backward\n    // * increment the counter if phys time does not advance\n    const lNew = Math.max(lOld, phys);\n    const cNew = lOld === lNew ? cOld + 1 : 0;\n\n    // check the result for drift and counter overflow\n    if (lNew - phys > config.maxDrift) {\n      throw new Timestamp.ClockDriftError(lNew, phys, config.maxDrift);\n    }\n    if (cNew > MAX_COUNTER) {\n      throw new Timestamp.OverflowError();\n    }\n\n    // repack the logical time/counter\n    clock.timestamp.setMillis(lNew);\n    clock.timestamp.setCounter(cNew);\n\n    return new Timestamp(\n      clock.timestamp.millis(),\n      clock.timestamp.counter(),\n      clock.timestamp.node(),\n    );\n  }\n\n  // Timestamp receive. Parses and merges a timestamp from a remote\n  // system with the local timeglobal uniqueness and monotonicity are","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/crdt/src/crdt/timestamp.ts#L197-L233","documentation":"Timestamp.send computes the next logical timestamp: it takes the max of the old logical time and the physical clock, incrementing the counter if physical time did not advance. If the resulting logical time exceeds the physical clock by more than config.maxDrift, ClockDriftError is thrown because the local clock has drifted too far ahead.","triggerScenarios":"sendTimestamp is called after the system clock was moved backward (e.g. NTP correction, timezone change, VM resume) while previously generated timestamps were far ahead of the current physical time by more than maxDrift.","commonSituations":"Laptop with a dead RTC clock; VM/container whose clock jumps back after snapshot restore; dual-boot systems with differing hardware clocks;NTP stepping the clock back by a large amount.","solutions":["Fix the system clock (enable NTP sync) and ensure it is accurate before generating timestamps.","Delete or reset the stored clock/timestamp state so counters restart from the corrected physical time (accepting potential sync conflicts).","Increase config.maxDrift if your environment legitimately has large clock variance (not recommended).","Avoid VM snapshot rollbacks on machines running the app."],"exampleFix":"// before\n// system clock jumped back; stored timestamp is ahead by hours\nconst ts = timestamp.send(); // throws ClockDriftError\n// after\n$ sudo timedatectl set-ntp true && sudo ntpdate pool.ntp.org\nconst ts = timestamp.send(); // succeeds once phys time catches up","handlingStrategy":"try-catch","validationCode":"const now = Date.now();\nif (lastLogicalTime - now > maxDrift) {\n  throw new Error('System clock is behind stored logical time; fix clock before sending timestamps');\n}","typeGuard":null,"tryCatchPattern":"import { Timestamp } from './timestamp';\ntry {\n  const ts = timestamp.send();\n} catch (err) {\n  if (err instanceof Timestamp.ClockDriftError) {\n    // resync system clock, then rebuild clock state from physical time\n    await resyncSystemClock();\n    resetClockFromPhysicalTime();\n  } else {\n    throw err;\n  }\n}","preventionTips":["Keep NTP time synchronization enabled on all machines running the app.","Avoid VM snapshot rollbacks and manual clock changes on app hosts.","Monitor clock skew in production environments.","Persist clock state (logical time/counter) so restarts don't compound drift."],"tags":["crdt","timestamp","clock-drift","sync"],"backgroundTag":"clock-drift","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}