Skip to content

fix: normalize non-finite desktop event properties - #656

Merged
marandaneto merged 2 commits into
mainfrom
fix/non-finite-event-properties-20261022
Oct 22, 2026
Merged

marandaneto merged 2 commits into
mainfrom
fix/non-finite-event-properties-20261022

Conversation

@marandaneto

@marandaneto marandaneto commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

💡 Motivation and Context

On opt-in Windows/Linux, numeric NaN and infinities in event properties were sent as the strings "NaN", "Infinity" and "-Infinity". Omit these numeric values from object members and use JSON null for array slots, without dropping events or changing array positions. Caller-supplied literal strings remain unchanged.

Normalize object members before deciding whether to omit them. This keeps buffered exception steps identical to their emitted form, so byte budgets do not falsely reject steps or evict valid older steps. Native Android/Apple and web wrappers are unchanged. No public API shape changes.

Related cross-SDK context: PostHog/posthog-python#1081. The shared exception-step contract requires identical budgeted, buffered and emitted forms. Shared specs do not currently prescribe a non-finite-number policy.

💚 How did you test it?

Reproduction-first Flutter tests cover all three numeric values directly and in nested maps, arrays and objects inside arrays, plus finite/bool/literal controls, input preservation and a healthy event in the same batch. Review regressions reproduced a 171-byte wire step rejected as 210 buffered bytes, premature eviction of fitting steps, and a buffered/wire mismatch. Those exact regressions pass with the fix.

Affected tests passed 170 cases. The full VM suite passed 1260 tests with one existing macOS platform skip. Desktop core passed 251 tests and compliance adapter tests passed 25. Dart formatting, analysis, public API snapshot and Kotlin/Swift formatting passed. Authoring audit and exact-HEAD branch autoreview found no blockers. No live telemetry or native executable build was used.

The earlier iOS CocoaPods job failed only the unchanged native decodable-snapshot XCTest in the available log. Its assertion diagnostics were not included. This desktop Dart change does not claim to fix that separate native failure; CI should rerun after the update.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

A Pi implementation agent used local shell/file tools, Flutter tests, read-only GitHub feedback APIs and isolated autoreview under maintainer direction. The follow-up reproduced the review claim and chose consistent object omission rather than counting null members that capture later deletes. Array slots and existing unrelated serialization behavior remain unchanged. Human review is required.

Human DRI: @marandaneto.

@marandaneto marandaneto self-assigned this Oct 22, 2026
@marandaneto
marandaneto marked this pull request as ready for review October 22, 2026 11:42
@marandaneto
marandaneto requested a review from a team as a code owner October 22, 2026 11:42

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PostHog Review · Standard · reviewed 15290dc: 1 Should fix.

This is the only automatic review while this pull request is a draft. Comment @posthog review to review new changes sooner. Otherwise, a review of the new changes runs when you mark it ready for review.

Comment thread posthog_flutter/lib/src/core/utils/utils.dart
@posthog posthog Bot removed the reviewhog label Oct 22, 2026
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

posthog-flutter Compliance Report

Date: 2026-10-22 14:44:39 UTC
Duration: 96796ms

✅ All Tests Passed!

45/45 tests passed


Capture Tests

✅ 29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 138ms
Format Validation.Event Has Uuid ✅ 121ms
Format Validation.Event Has Lib Properties ✅ 117ms
Format Validation.Distinct Id Is String ✅ 115ms
Format Validation.Token Is Present ✅ 117ms
Format Validation.Custom Properties Preserved ✅ 117ms
Format Validation.Event Has Timestamp ✅ 118ms
Retry Behavior.Retries On 503 ✅ 5333ms
Retry Behavior.Does Not Retry On 400 ✅ 2119ms
Retry Behavior.Does Not Retry On 401 ✅ 2118ms
Retry Behavior.Respects Retry After Header ✅ 8126ms
Retry Behavior.Implements Backoff ✅ 15448ms
Retry Behavior.Retries On 500 ✅ 5227ms
Retry Behavior.Retries On 502 ✅ 5224ms
Retry Behavior.Retries On 504 ✅ 5225ms
Retry Behavior.Max Retries Respected ✅ 15439ms
Deduplication.Generates Unique Uuids ✅ 125ms
Deduplication.Preserves Uuid On Retry ✅ 5224ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10339ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5234ms
Deduplication.No Duplicate Events In Batch ✅ 125ms
Deduplication.Different Events Have Different Uuids ✅ 118ms
Compression.Sends Gzip When Enabled ✅ 118ms
Batch Format.Uses Proper Batch Structure ✅ 114ms
Batch Format.Flush With No Events Sends Nothing ✅ 109ms
Batch Format.Multiple Events Batched Together ✅ 123ms
Error Handling.Does Not Retry On 403 ✅ 2117ms
Error Handling.Does Not Retry On 413 ✅ 2118ms
Error Handling.Retries On 408 ✅ 5226ms

Feature_Flags Tests

✅ 16/16 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 13ms
Request Payload.Flags Request Uses V2 Query Param ✅ 10ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 11ms
Request Payload.Flags Request Omits Authorization Header ✅ 9ms
Request Payload.Token In Flags Body Matches Init ✅ 9ms
Request Payload.Groups Round Trip ✅ 9ms
Request Payload.Groups Default To Empty Object ✅ 9ms
Request Payload.Person Properties Distinct Id Auto Populated When Caller Omits It ✅ 9ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 9ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 9ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 10ms
Request Lifecycle.No Flags Request On Init Alone ✅ 4ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 112ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 18ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 10ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 116ms

@turnipdabeets turnipdabeets left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

toJsonValue checks for null before converting, so a NaN member passes the dropNullMembers filter and is then emitted as null. Event properties end up sending "ratio": null, while the same field is omitted from the buffered exception step, and the step's byte budget counts members that are stripped before sending.

Should we filter on the converted value in toJsonValue's map branch (convert first, then drop if null when dropNullMembers)? That keeps buffered steps byte-identical to what's sent and makes NaN members follow the same omission rule as explicit nulls.

@marandaneto marandaneto changed the title fix: serialize non-finite desktop event properties as null fix: normalize non-finite desktop event properties Oct 22, 2026
@marandaneto
marandaneto enabled auto-merge (squash) October 22, 2026 14:52
@marandaneto
marandaneto merged commit 94ca664 into main Oct 22, 2026
30 of 31 checks passed
@marandaneto
marandaneto deleted the fix/non-finite-event-properties-20261022 branch October 22, 2026 14:57
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