{"record":{"id":"30be961b03160c75","repo":"actualbudget/actual","slug":"timestamp-invaliderror-data-timestamp","errorCode":null,"errorMessage":"Timestamp.InvalidError: ${data.timestamp}","messagePattern":"Timestamp\\.InvalidError: (.+?)","errorType":"exception","errorClass":"Timestamp.InvalidError","httpStatus":null,"severity":"critical","filePath":"packages/crdt/src/crdt/timestamp.ts","lineNumber":70,"sourceCode":"    merkle: clock.merkle,\n  });\n}\n\nexport function deserializeClock(clock: string): Clock {\n  let data;\n  try {\n    data = JSON.parse(clock);\n  } catch {\n    data = {\n      timestamp: '1970-01-01T00:00:00.000Z-0000-' + makeClientId(),\n      merkle: {},\n    };\n  }\n\n  const ts = Timestamp.parse(data.timestamp);\n\n  if (!ts) {\n    throw new Timestamp.InvalidError(data.timestamp);\n  }\n\n  return {\n    timestamp: MutableTimestamp.from(ts),\n    merkle: data.merkle,\n  };\n}\n\nexport function makeClientId() {\n  return uuidv4().replace(/-/g, '').slice(-16);\n}\n\nconst config = {\n  // Allow 5 minutes of clock drift\n  maxDrift: 5 * 60 * 1000,\n};\n\nconst MAX_COUNTER = parseInt('0xFFFF');","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/crdt/src/crdt/timestamp.ts#L52-L88","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nconst clock = deserializeClock({ merkle: trie }); // timestamp missing\n// after\nconst clock = deserializeClock({ timestamp: '2024-01-01T00:00:00.000Z-0000-0000FFFFFFFFFFFFFFFF', merkle: trie });","handlingStrategy":"try-catch","validationCode":"function hasValidTimestamp(data: unknown): data is { timestamp: string; merkle: object } {\n  const d = data as { timestamp?: unknown };\n  return typeof d?.timestamp === 'string' && d.timestamp.length > 0;\n}","typeGuard":"import { Timestamp } from './timestamp';\nfunction isDeserializableClock(data: unknown): boolean {\n  const d = data as { timestamp?: string };\n  return typeof d?.timestamp === 'string' && Timestamp.parse(d.timestamp) != null;\n}","tryCatchPattern":"let clock;\ntry {\n  clock = deserializeClock(data);\n} catch (err) {\n  if (err instanceof Timestamp.InvalidError) {\n    // discard bad clock and rebuild from messages\n    clock = deserializeClock({ timestamp: freshTimestamp(), merkle: {} });\n  } else {\n    throw err;\n  }\n}","preventionTips":["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)."],"tags":["crdt","timestamp","deserialization","sync"],"backgroundTag":"invalid-timestamp-format","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}