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

  1. Inspect the clock data's timestamp field and fix or remove the malformed record.
  2. Delete the stored clock so it is rebuilt from the full message history (merkle recomputation).
  3. Restore the database from a backup.
  4. 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

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


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/30be961b03160c75. Report an issue: GitHub.