clockworklabs/SpacetimeDB · error · Error
`fromCounterV7` uuid `counter` must be non-negative
Error message
`fromCounterV7` uuid `counter` must be non-negative
What it means
Uuid.fromCounterV7 expects a mutable counter object { value: number } holding a 31-bit non-negative count. The value is embedded into the UUID and then incremented with wraparound ((value + 1) & 0x7fffffff). A negative value has no representation in this scheme, so it is rejected up front.
Source
Thrown at crates/bindings-typescript/src/lib/uuid.ts:163
*
* const uuid = Uuid.fromCounterV7(counter, now, randomBytes);
*
* console.assert(
* 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);View on GitHub (pinned to 524b4487d9)
Solutions
- Initialize the counter at 0 and only ever let fromCounterV7 increment it
- Clamp before calling: counter.value = Math.max(0, counter.value)
- Validate counter.value >= 0 at the boundary if the value comes from storage or the network
Example fix
// before
const counter = { value: -1 }; // 'unset' sentinel
const uuid = Uuid.fromCounterV7(counter, now, rand); // throws
// after
const counter = { value: 0 };
const uuid = Uuid.fromCounterV7(counter, now, rand); Defensive patterns
Strategy: validation
Validate before calling
if (counter.value < 0) counter.value = 0; // or reject the input const uuid = Uuid.fromCounterV7(counter, now, rand);
Type guard
function isNonNegativeCounter(c: unknown): c is { value: number } {
return (
typeof c === 'object' &&
c !== null &&
typeof (c as { value?: unknown }).value === 'number' &&
(c as { value: number }).value >= 0
);
} Prevention
- Initialize counters at 0; never use -1 as a sentinel
- Let fromCounterV7 be the only code that mutates the counter (it increments with wraparound)
When it happens
Trigger: Passing { value: -1 } or any negative counter to Uuid.fromCounterV7(counter, now, randomBytes); a counter that underflowed through decrement logic or was loaded from a source that permits negatives.
Common situations: Initializing the counter to -1 as an 'unset' sentinel; decrementing the counter on some teardown path; deserializing a persisted counter that stored a signed value.
Related errors
- `fromCounterV7` requires `randomBytes.length == 4`
- `fromCounterV7` `timestamp` before unix epoch
- Invalid hex UUID
- Cannot convert ${typeof value} to ${what}: expected bigint,
- Invalid UUID: must be between 0 and `MAX_UUID_BIGINT`
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/7f114a32f74b9b29.
Report an issue: GitHub.