clockworklabs/SpacetimeDB · error · Error

`fromCounterV7` `timestamp` before unix epoch

Error message

`fromCounterV7` `timestamp` before unix epoch

What it means

A UUIDv7 layout begins with a 48-bit unix-millisecond timestamp, which cannot represent times before 1970-01-01. fromCounterV7 checks now.__timestamp_micros_since_unix_epoch__ < 0 and rejects any pre-epoch Timestamp before encoding.

Solutions

  1. Build the Timestamp from the current time (e.g. derived from Date.now()) instead of a computed historical value
  2. Validate micros-since-epoch >= 0 before calling fromCounterV7
  3. Clamp incoming times to the unix epoch minimum at your data boundary and log when clamping occurs

Example fix

// before
const now = tsFromMicros(-1_000_000); // before unix epoch
const id = Uuid.fromCounterV7(counter, now, rand); // throws

// after
const now = tsFromMicros(Date.now() * 1000);
const id = Uuid.fromCounterV7(counter, now, rand);
Defensive patterns

Strategy: validation

Validate before calling

if (now.__timestamp_micros_since_unix_epoch__ < 0) {
  throw new Error('timestamp must be at or after the unix epoch');
}
const id = Uuid.fromCounterV7(counter, now, randomBytes);

Prevention

When it happens

Trigger: Passing a Timestamp built from negative milliseconds/micros: a pre-1970 Date, date arithmetic that underflows (subtracting a duration from a small timestamp), or a mocked clock returning negative time.

Common situations: Test fixtures with historical dates; timestamps computed as differences from an epoch that go negative; devices or CI containers with misconfigured system clocks; data migrated from systems using non-unix epochs.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/c9f35270cafea9b3. Report an issue: GitHub.

Appendix: source

Thrown at crates/bindings-typescript/src/lib/uuid.ts:167

   *   uuid.toString() === "0000647e-5180-7000-8000-000200000000"
   * );
   * ```
   */
  static fromCounterV7(
    counter: { value: number },
    now: Timestamp,
    randomBytes: Uint8Array
  ): Uuid {
    if (randomBytes.length !== 4) {
      throw new Error('`fromCounterV7` requires `randomBytes.length == 4`');
    }

    if (counter.value < 0) {
      throw new Error('`fromCounterV7` uuid `counter` must be non-negative');
    }

    if (now.__timestamp_micros_since_unix_epoch__ < 0) {
      throw new Error('`fromCounterV7` `timestamp` before unix epoch');
    }

    // 31-bit monotonic counter with wraparound
    const counterVal = counter.value;
    counter.value = (counterVal + 1) & 0x7fff_ffff;

    // 48-bit unix timestamp (ms)
    const tsMs = now.toMillis() & 0xffff_ffff_ffffn;

    const bytes = new Uint8Array(16);

    // unix_ts_ms (48 bits)
    bytes[0] = Number((tsMs >> 40n) & 0xffn);
    bytes[1] = Number((tsMs >> 32n) & 0xffn);
    bytes[2] = Number((tsMs >> 24n) & 0xffn);
    bytes[3] = Number((tsMs >> 16n) & 0xffn);
    bytes[4] = Number((tsMs >> 8n) & 0xffn);
    bytes[5] = Number(tsMs & 0xffn);

View on GitHub (pinned to 6dee26c6ef)