actualbudget/actual · critical · Timestamp.ClockDriftError
Timestamp.ClockDriftError
Error message
Timestamp.ClockDriftError
What it means
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.
Source
Thrown at packages/crdt/src/crdt/timestamp.ts:215
return null;
}
// retrieve the local wall time
const phys = Date.now();
// unpack the clock.timestamp logical time and counter
const lOld = clock.timestamp.millis();
const cOld = clock.timestamp.counter();
// calculate the next logical time and counter
// * ensure that the logical time never goes backward
// * increment the counter if phys time does not advance
const lNew = Math.max(lOld, phys);
const cNew = lOld === lNew ? cOld + 1 : 0;
// check the result for drift and counter overflow
if (lNew - phys > config.maxDrift) {
throw new Timestamp.ClockDriftError(lNew, phys, config.maxDrift);
}
if (cNew > MAX_COUNTER) {
throw new Timestamp.OverflowError();
}
// repack the logical time/counter
clock.timestamp.setMillis(lNew);
clock.timestamp.setCounter(cNew);
return new Timestamp(
clock.timestamp.millis(),
clock.timestamp.counter(),
clock.timestamp.node(),
);
}
// Timestamp receive. Parses and merges a timestamp from a remote
// system with the local timeglobal uniqueness and monotonicity areView on GitHub (pinned to d4334cb6e6)
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.
Example fix
// before // system clock jumped back; stored timestamp is ahead by hours const ts = timestamp.send(); // throws ClockDriftError // after $ sudo timedatectl set-ntp true && sudo ntpdate pool.ntp.org const ts = timestamp.send(); // succeeds once phys time catches up
Defensive patterns
Strategy: try-catch
Validate before calling
const now = Date.now();
if (lastLogicalTime - now > maxDrift) {
throw new Error('System clock is behind stored logical time; fix clock before sending timestamps');
} Try / catch
import { Timestamp } from './timestamp';
try {
const ts = timestamp.send();
} catch (err) {
if (err instanceof Timestamp.ClockDriftError) {
// resync system clock, then rebuild clock state from physical time
await resyncSystemClock();
resetClockFromPhysicalTime();
} else {
throw err;
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Timestamp.InvalidError: ${data.timestamp}
- TrieNode for key ${k} could not be found
- getSyncError(result.error.reason, localBudget.id, result.err
- Sync ID is required for sync ${flag}. Set --sync-id or ACTUA
- Could not resolve on-disk budget id for syncId ${syncId} aft
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/34156491e201fa84.
Report an issue: GitHub.