Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-uuidv7-clamp-nextuint32.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@posthog/core': patch

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: This changeset only releases @posthog/core, so consumers can publish without requiring the fixed core version. Please also add patch entries for every package whose runtime directly uses this UUIDv7 generator, inherits the core capture path, or ships the affected posthog-node: @posthog/ai, @posthog/convex, @posthog/mcp, @posthog/next, @posthog/nuxt, posthog-js-lite, posthog-node, and posthog-react-native.

---

Stop crashing when the environment's `Math.random()` misbehaves. The vendored UUIDv7 generator builds its random fields from a `Math.random()`-based `nextUint32()`, and a nonconformant implementation that returns a value of 1 or greater, or NaN, pushed those fields out of range, so `fromFieldsV7` threw `RangeError: invalid field value` on every event captured. On React Native this is not hypothetical: Hermes implements `Math.random` with C++ `std::uniform_real_distribution`, which is documented to occasionally return its upper bound, and affected Android devices crash-looped on startup during the SDK's internal event-queue flush — a path applications cannot wrap in a try/catch. `nextUint32()` now clamps its result to a valid unsigned 32-bit integer (`>>> 0`), so a bad random value degrades UUID entropy for that id instead of taking the app down; the timestamp bits are untouched and generated ids remain spec-valid UUIDv7.
26 changes: 26 additions & 0 deletions packages/core/src/vendor/uuidv7.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { V7Generator } from './uuidv7'

describe('uuidv7 default RNG', () => {
const realRandom = Math.random

afterEach(() => {
Math.random = realRandom
})

it.each([
['exactly 1.0', 1.0],
['greater than 1', 1.5],
['NaN', NaN],
])('does not throw when Math.random() returns %s', (_label, value) => {
Math.random = () => value
const generator = new V7Generator()
const uuid = generator.generate().toString()
expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/)
})

it('still generates well-formed v7 UUIDs with the real Math.random', () => {
const generator = new V7Generator()
const uuid = generator.generate().toString()
expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/)
})
})
10 changes: 8 additions & 2 deletions packages/core/src/vendor/uuidv7.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,9 +429,15 @@ const getDefaultRandom = (): { nextUint32(): number } => {
// };
// }
return {
// Clamp to a valid uint32: a nonconformant Math.random() that returns >= 1 or NaN
// (e.g. Hermes on Android implements Math.random with C++ std::uniform_real_distribution,
// which is documented to occasionally return its upper bound) would otherwise overflow the
// field ranges and make fromFieldsV7 throw `RangeError: invalid field value` on every
// generate call, crashing React Native apps during the internal event-queue flush.
nextUint32: (): number =>
Math.trunc(Math.random() * 0x1_0000) * 0x1_0000 +
Math.trunc(Math.random() * 0x1_0000),
(Math.trunc(Math.random() * 0x1_0000) * 0x1_0000 +
Math.trunc(Math.random() * 0x1_0000)) >>>
0,
};
};

Expand Down