clockworklabs/SpacetimeDB · error · Error
`fromCounterV7` `timestamp` before unix epoch
Error message
`fromCounterV7` `timestamp` before unix epoch
What it means
Uuidv7 encodes the timestamp as a 48-bit unix_ts_ms field counting from 1970-01-01. fromCounterV7 rejects any Timestamp whose __timestamp_micros_since_unix_epoch__ is negative, because pre-epoch times cannot be encoded in that unsigned field.
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 524b4487d9)
Solutions
- Use the current time: Timestamp.fromMillis(BigInt(Date.now())) or an equivalent now() helper
- Guard the call: if (now.__timestamp_micros_since_unix_epoch__ < 0) use a non-negative fallback or reject the input
- Fix the upstream clock/date source that produced the pre-epoch value
Example fix
// before const now = Timestamp.fromMillis(-1n); const uuid = Uuid.fromCounterV7(counter, now, rand); // throws // after const now = Timestamp.fromMillis(BigInt(Date.now())); const uuid = Uuid.fromCounterV7(counter, now, rand);
Defensive patterns
Strategy: validation
Validate before calling
if (now.__timestamp_micros_since_unix_epoch__ < 0) {
throw new RangeError('timestamp must be on or after the unix epoch');
}
const uuid = Uuid.fromCounterV7(counter, now, rand); Type guard
function isEpochOrLater(t: Timestamp): boolean {
return t.__timestamp_micros_since_unix_epoch__ >= 0;
} Prevention
- Derive timestamps from Date.now() or Timestamp.fromMillis(BigInt(Date.now()))
- Keep mocked test clocks at or after 1970-01-01
When it happens
Trigger: Passing a Timestamp built from negative microseconds (e.g. Timestamp.fromMillis(-1n)) or from a Date before 1970 to Uuid.fromCounterV7.
Common situations: Test/mocked clocks set to zero or a negative value; arithmetic on timestamps that underflows below the epoch; feeding historical dates (pre-1970) into id generation.
Related errors
- `fromCounterV7` requires `randomBytes.length == 4`
- `fromCounterV7` uuid `counter` must be non-negative
- timestamp before unix epoch
- Invalid UUID: must be between 0 and `MAX_UUID_BIGINT`
- UUID v4 requires 16 bytes
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/c9f35270cafea9b3.
Report an issue: GitHub.