Skip to content

docs(capture): normalize non-finite numeric event properties to JSON null - #140

Draft
posthog[bot] wants to merge 3 commits into
mainfrom
posthog/capture-non-finite-numbers
Draft

posthog[bot] wants to merge 3 commits into
mainfrom
posthog/capture-non-finite-numbers

Conversation

@posthog

@posthog posthog Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Why

Six SDKs independently fixed the same bug in the same week: an event property holding a non-finite floating-point number (NaN, +Infinity, -Infinity) produced a payload that ingestion rejected, or that the SDK itself refused to serialize — so the event, and in several SDKs the whole batch around it, was lost.

Four of those PRs say in their own description that the shared capture, HTTP-client and event-batcher specs do not prescribe a policy here, so each SDK had to pick one. They converged, which is the point — but nothing in this repo holds the line for the next SDK or the next port.

Source changes:

  • posthog-python #1081 — emitted the bare tokens NaN / Infinity; strict parsers in the ingestion path rejected the batch
  • posthog-ruby #315 — events lost during serialization, including nested values
  • posthog-dotnet #348 — the batch failed to serialize, taking healthy neighboring events with it
  • posthog-unity #150 — invalid JSON in the queue; reload truncated following properties or stalled
  • posthog-godot #1 — the whole event was dropped at strict admission
  • posthog-flutter #656 — desktop sent the strings "NaN" / "Infinity" / "-Infinity"

posthog-js never had the bug: JSON.stringify already turns a non-finite number into null.

What changed

One requirement added to openspec/specs/capture/spec.md, with the change archived under openspec/changes/archive/2026-10-23-specify-non-finite-number-serialization/:

Capture normalizes non-finite numeric property values. A non-finite number is replaced with JSON null; the bare NaN/Infinity tokens and the string forms are both forbidden. A non-finite value never costs data — not the call, not the event, not the batch, not a healthy neighbor. Normalization is recursive, keeps array indices, does not mutate the caller's container, and applies to queued and immediate capture, after before_send, and on every disk serialization (a persisted NaN is unparseable on restore). Byte budgets measure the normalized form, so buffered and emitted forms agree. Literal strings like "NaN" and finite values of any magnitude are untouched, and unsupported types keep their existing error behavior.

Uncertain / left out

  • Object-member placement is deliberately left open. Most of these SDKs serialize the normalized null as an object member; posthog-flutter omits the member instead, so the result matches the existing Capture drops null-valued object properties requirement — which says a null-valued member must not be serialized as JSON null. Both readings are defensible and the answer is a judgment call about that interaction, so this PR records the divergence rather than picking a winner. Worth a human decision. Array slots have no such latitude and are specified.
  • No acceptance scenarios. The capture steps carry properties as a JSON document string, and JSON cannot express NaN or infinity, so executable coverage needs new step vocabulary in the harness. Out of scope here.
  • posthog-ios, posthog-android, posthog-go, posthog-php, posthog-elixir, posthog-rs, posthog-java and posthog-kmp were not audited against the new requirement. Per repo convention that is not a prerequisite for archiving the spec change.

Validation

openspec validate specify-non-finite-number-serialization --type change --strict passes with only the repo's usual long-requirement warning. The repo-wide openspec validate --specs --strict run is unchanged at 32 passed / 32 failed — capture was already in the failing bucket for pre-existing long-requirement warnings, and no spec newly fails. These are specification checks, not executed SDK conformance tests.


Created with PostHog Desktop

🤖 Generated with Claude Code

dustinbyrne and others added 3 commits October 8, 2026 13:27
…null

Six SDKs independently fixed the same data-loss bug in one week: a NaN or
infinity in an event property produced a payload ingestion rejected, or one
the SDK could not serialize at all, losing the event and often its batch.
Four of those PRs noted that the shared specs prescribe no policy here.

Adds a capture requirement fixing the no-data-loss and array-slot rules, and
records the one point where the SDKs still diverge (object-member placement)
instead of picking a winner.

Generated-By: PostHog Desktop
Task-Id: b122d073-a451-4d4c-8f41-394e62cb901c
@eli-r-ph

Copy link
Copy Markdown
Contributor

Noted, updated: posthog-go v2 now sends non-finite numbers as null too (PostHog/posthog-go#394), and the capture v1 spec in #146 now requires null for every SDK, matching this PR.

@turnipdabeets
turnipdabeets changed the base branch from docs/acceptance-step-catalogue to main October 27, 2026 22:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants