actualbudget/actual · critical · Timestamp.InvalidError
Timestamp.InvalidError: ${data.timestamp}
Error message
Timestamp.InvalidError: ${data.timestamp} What it means
deserializeClock converts stored/synced clock data back into a Timestamp, validating data.timestamp with Timestamp.parse. If parsing fails, Timestamp.InvalidError is thrown, indicating the serialized timestamp string is malformed.
Source
Thrown at packages/crdt/src/crdt/timestamp.ts:70
merkle: clock.merkle,
});
}
export function deserializeClock(clock: string): Clock {
let data;
try {
data = JSON.parse(clock);
} catch {
data = {
timestamp: '1970-01-01T00:00:00.000Z-0000-' + makeClientId(),
merkle: {},
};
}
const ts = Timestamp.parse(data.timestamp);
if (!ts) {
throw new Timestamp.InvalidError(data.timestamp);
}
return {
timestamp: MutableTimestamp.from(ts),
merkle: data.merkle,
};
}
export function makeClientId() {
return uuidv4().replace(/-/g, '').slice(-16);
}
const config = {
// Allow 5 minutes of clock drift
maxDrift: 5 * 60 * 1000,
};
const MAX_COUNTER = parseInt('0xFFFF');View on GitHub (pinned to d4334cb6e6)
Solutions
- Inspect the clock data's timestamp field and fix or remove the malformed record.
- Delete the stored clock so it is rebuilt from the full message history (merkle recomputation).
- Restore the database from a backup.
- Align client/server versions so timestamp serialization formats match.
Example fix
// before
const clock = deserializeClock({ merkle: trie }); // timestamp missing
// after
const clock = deserializeClock({ timestamp: '2024-01-01T00:00:00.000Z-0000-0000FFFFFFFFFFFFFFFF', merkle: trie }); Defensive patterns
Strategy: try-catch
Validate before calling
function hasValidTimestamp(data: unknown): data is { timestamp: string; merkle: object } {
const d = data as { timestamp?: unknown };
return typeof d?.timestamp === 'string' && d.timestamp.length > 0;
} Type guard
import { Timestamp } from './timestamp';
function isDeserializableClock(data: unknown): boolean {
const d = data as { timestamp?: string };
return typeof d?.timestamp === 'string' && Timestamp.parse(d.timestamp) != null;
} Try / catch
let clock;
try {
clock = deserializeClock(data);
} catch (err) {
if (err instanceof Timestamp.InvalidError) {
// discard bad clock and rebuild from messages
clock = deserializeClock({ timestamp: freshTimestamp(), merkle: {} });
} else {
throw err;
}
} Prevention
- Validate sync payloads before deserializing (timestamp present and string-typed).
- Keep crdt versions aligned across clients and server to avoid format drift.
- Back up the budget database before manual repairs.
- Reject truncated JSON payloads at the transport layer (checksum/length checks).
When it happens
Trigger: clock() or hash() receive clock data whose timestamp field is missing, null, truncated, or in an unexpected format (e.g. from an incompatible serialization version or corrupted storage).
Common situations: Corrupted budget database rows; hand-edited clock data; syncing payloads produced by a different crdt version with a changed timestamp format; JSON payloads truncated in transit.
Related errors
- Timestamp.ClockDriftError
- TrieNode for key ${k} could not be found
- Sync ID is required for sync ${flag}. Set --sync-id or ACTUA
- Could not resolve on-disk budget id for syncId ${syncId} aft
- Timestamp.OverflowError
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/30be961b03160c75.
Report an issue: GitHub.