{"record":{"id":"b333235366684efa","repo":"clockworklabs/SpacetimeDB","slug":"uuid-counter-must-be-non-negative","errorCode":null,"errorMessage":"uuid counter must be non-negative","messagePattern":"uuid counter must be non-negative","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"crates/bindings-csharp/BSATN.Runtime/BSATN/Uuid.cs","lineNumber":132,"sourceCode":"    /// Thrown if <paramref name=\"randomBytes\"/> is not exactly 4 bytes long, or <paramref name=\"now\"/> is  before unix epoch.\n    /// </exception>\n    /// <returns>\n    /// A <see cref=\"Uuid\"/> `v7`.\n    /// </returns>\n    public static Uuid FromCounterV7(\n        ref int counter,\n        Timestamp now,\n        ReadOnlySpan<byte> randomBytes // must be length 4\n    )\n    {\n        if (randomBytes.Length != 4)\n        {\n            throw new ArgumentException(\"randomBytes must be exactly 4 bytes\", nameof(randomBytes));\n        }\n\n        if (counter < 0)\n        {\n            throw new InvalidOperationException(\"uuid counter must be non-negative\");\n        }\n\n        if (now.MicrosecondsSinceUnixEpoch < 0)\n        {\n            throw new ArgumentException(\"timestamp before unix epoch\", nameof(now));\n        }\n        var unixTsMs = now.MicrosecondsSinceUnixEpoch / 1_000;\n\n        // monotonic 31-bit\n        var counterVal = counter;\n        counter = (counter + 1) & 0x7FFF_FFFF;\n\n        Span<byte> bytes = stackalloc byte[16];\n\n        // unix_ts_ms (48 bits, big-endian)\n        var ts = unixTsMs & 0x0000_FFFF_FFFF_FFFFL;\n        bytes[0] = (byte)(ts >> 40);\n        bytes[1] = (byte)(ts >> 32);","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-csharp/BSATN.Runtime/BSATN/Uuid.cs#L114-L150","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nint counter = -1; // \"uninitialized\" sentinel\nvar id = Uuid.FromCounterV7(ref counter, now, rand4); // throws\n\n// after\nint counter = 0;\nvar id = Uuid.FromCounterV7(ref counter, now, rand4);","handlingStrategy":"validation","validationCode":"if (counter < 0 || counter > 0x7FFF_FFFF)\n{\n    throw new ArgumentOutOfRangeException(nameof(counter), $\"Counter out of 31-bit range: {counter}\");\n}\nvar id = Uuid.FromCounterV7(ref counter, now, randomBytes);","typeGuard":null,"tryCatchPattern":"try\n{\n    id = Uuid.FromCounterV7(ref counter, now, randomBytes);\n}\ncatch (InvalidOperationException) when (counter < 0)\n{\n    counter = 0; // reinitialize corrupted state and retry once\n    id = Uuid.FromCounterV7(ref counter, now, randomBytes);\n}","preventionTips":["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"],"tags":["csharp","uuid","uuidv7","argument-validation","spacetimedb"],"backgroundTag":"invalid-argument-value","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}