clockworklabs/SpacetimeDB · error · InvalidOperationException
uuid counter must be non-negative
Error message
uuid counter must be non-negative
What it means
Thrown by Uuid.FromCounterV7(ref counter, now, randomBytes) when the caller-supplied counter is negative. UUIDv7 encodes a 31-bit monotonic counter into the ID, so the counter must be in [0, 2^31-1]; the method itself increments it and wraps with `& 0x7FFF_FFFF`, so a negative value can only originate from how the caller initialized or mutated the shared variable.
Source
Thrown at crates/bindings-csharp/BSATN.Runtime/BSATN/Uuid.cs:132
/// Thrown if <paramref name="randomBytes"/> is not exactly 4 bytes long, or <paramref name="now"/> is before unix epoch.
/// </exception>
/// <returns>
/// A <see cref="Uuid"/> `v7`.
/// </returns>
public static Uuid FromCounterV7(
ref int counter,
Timestamp now,
ReadOnlySpan<byte> randomBytes // must be length 4
)
{
if (randomBytes.Length != 4)
{
throw new ArgumentException("randomBytes must be exactly 4 bytes", nameof(randomBytes));
}
if (counter < 0)
{
throw new InvalidOperationException("uuid counter must be non-negative");
}
if (now.MicrosecondsSinceUnixEpoch < 0)
{
throw new ArgumentException("timestamp before unix epoch", nameof(now));
}
var unixTsMs = now.MicrosecondsSinceUnixEpoch / 1_000;
// monotonic 31-bit
var counterVal = counter;
counter = (counter + 1) & 0x7FFF_FFFF;
Span<byte> bytes = stackalloc byte[16];
// unix_ts_ms (48 bits, big-endian)
var ts = unixTsMs & 0x0000_FFFF_FFFF_FFFFL;
bytes[0] = (byte)(ts >> 40);
bytes[1] = (byte)(ts >> 32);View on GitHub (pinned to 524b4487d9)
Solutions
- Initialize the counter to 0 (or any non-negative value) before the first FromCounterV7 call and never decrement it yourself
- Audit every write to the shared counter variable - only FromCounterV7 should advance it
- When resuming from persisted state, sanitize with counter &= 0x7FFF_FFFF (or reject the state) before generating again
- Wrap the call in try/catch (InvalidOperationException) to detect corrupt counter state early and reinitialize
Example fix
// before int counter = -1; // "uninitialized" sentinel var id = Uuid.FromCounterV7(ref counter, now, rand4); // throws // after int counter = 0; var id = Uuid.FromCounterV7(ref counter, now, rand4);
Defensive patterns
Strategy: validation
Validate before calling
if (counter < 0 || counter > 0x7FFF_FFFF)
{
throw new ArgumentOutOfRangeException(nameof(counter), $"Counter out of 31-bit range: {counter}");
}
var id = Uuid.FromCounterV7(ref counter, now, randomBytes); Try / catch
try
{
id = Uuid.FromCounterV7(ref counter, now, randomBytes);
}
catch (InvalidOperationException) when (counter < 0)
{
counter = 0; // reinitialize corrupted state and retry once
id = Uuid.FromCounterV7(ref counter, now, randomBytes);
} Prevention
- Initialize every FromCounterV7 counter to 0 at declaration and never write to it elsewhere
- Do not use -1 sentinels for counters - use a nullable int wrapper if you need 'not started' semantics
- Mask persisted counters with & 0x7FFF_FFFF when resuming generation
When it happens
Trigger: Calling Uuid.FromCounterV7 with a counter initialized to -1 as an "uninitialized" sentinel; sharing the ref counter with code that decrements it or lets an int overflow into negative territory before the call; restoring a corrupted counter from persisted state.
Common situations: Porting sample code that used -1 sentinels; concurrent generation where another thread mutates the counter; counter state round-tripped through storage/config and re-read with a sign or parsing bug.
Related errors
- timestamp before unix epoch
- Unsupported --dotnet-version {version}. Supported values: 8,
- Unknown UUID version
- Expected ConnectionId hex string to be 32 characters long, b
- Expected Identity hex string to be 64 characters long, but w
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/b333235366684efa.
Report an issue: GitHub.