actualbudget/actual · critical · SyncError
clock-drift
clock-drift
Error message
clock-drift
What it means
A SyncError thrown by receiveMessages() when Timestamp.recv() detects a clock drift: a message arrives whose timestamp appears to come from the local node's future, meaning this device's clock is behind or was changed backwards. Because hybrid logical clocks can't safely accept such messages, sync aborts with 'clock-drift'.
Source
Thrown at packages/loot-core/src/server/sync/index.ts:454
const tables = getTablesFromMessages(messages.filter(msg => !msg.old));
app.events.emit('sync', {
type: 'applied',
tables,
data: newData,
prevData: oldData,
});
return messages;
});
export function receiveMessages(messages: Message[]): Promise<Message[]> {
try {
messages.forEach(msg => {
Timestamp.recv(msg.timestamp);
});
} catch (e) {
if (e instanceof Timestamp.ClockDriftError) {
throw new SyncError('clock-drift');
}
throw e;
}
return runMutator(() => applyMessages(messages));
}
async function errorHandler(e: Error) {
captureException(e);
if (e instanceof SyncError) {
if (e.reason === 'invalid-schema') {
// We know this message came from a local modification, and it
// couldn't apply, which doesn't make any sense. Must be a bug
// in the code. Send a specific error type for it for a custom
// message.
app.events.emit('sync', {
type: 'error',View on GitHub (pinned to d4334cb6e6)
Solutions
- Fix the system clock: enable automatic NTP time sync and let it correct the time
- After correcting the clock, restart the app and retry the sync
- Check that the time zone and hardware clock (VM/WSL users) are configured consistently
- If drift persists, compare the device time against time.is / NTP and reboot after syncing once corrected
Example fix
// before: clock behind await fullSync(); // clock-drift // after: resync system time sudo timedatectl set-ntp true && sudo timedatectl status // then restart the app and sync
Defensive patterns
Strategy: try-catch
Validate before calling
const skewMs = Date.now() - localHlcNowMs();
if (skewMs < -60_000) {
throw new Error('System clock appears behind; fix time before syncing');
} Type guard
function isClockDrift(e: unknown): e is SyncError {
return e instanceof SyncError && e.reason?.code === 'clock-drift';
} Try / catch
try {
await fullSync();
} catch (e) {
if (isClockDrift(e)) {
await resyncSystemClock(); // enable NTP, correct time
await fullSync();
} else throw e;
} Prevention
- Enable automatic NTP time sync on all devices
- Avoid VM snapshot restores of running clients
- Verify time before syncing after long offline periods
- Monitor clock skew in WSL/containers
When it happens
Trigger: receiveMessages() iterates messages from the server and calls Timestamp.recv(msg.timestamp); if any timestamp exceeds the local clock's allowed bound, Timestamp.ClockDriftError is thrown and converted to SyncError('clock-drift').
Common situations: System clock was set backwards (manual change, dead CMOS battery, VM snapshot restore, timezone/NTP misconfiguration); syncing a budget that received messages from another device while this device was paused/restored from a snapshot.
Related errors
- Sync ID is required for sync ${flag}. Set --sync-id or ACTUA
- Could not resolve on-disk budget id for syncId ${syncId} aft
- TrieNode for key ${k} could not be found
- Timestamp.InvalidError: ${data.timestamp}
- Timestamp.ClockDriftError
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/96671047128ece56.
Report an issue: GitHub.