Skip to content

fix(flags): read an empty variant as disabled on every platform - #711

Merged
turnipdabeets merged 3 commits into
chore/v6-posthog-ios-4from
fix/v6-empty-variant-disabled
Nov 9, 2026
Merged

turnipdabeets merged 3 commits into
chore/v6-posthog-ios-4from
fix/v6-empty-variant-disabled

Conversation

@turnipdabeets

@turnipdabeets turnipdabeets commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

A flag whose variant is the empty string "" read as disabled from some APIs and enabled from others. posthog-ios #1001 made iOS isFeatureEnabled return false for it (review thread), Android already did, but getFeatureFlagResult().enabled stayed true on both. posthog-js (browser) is already consistent. The iOS and Android 4.0 PRs make native enabled false; this PR fixes the Dart core used on Windows and Linux and rewrites the 6.0 docs.

  • iOS, macOS, Android: no Dart change. isFeatureEnabled() with defaultValue reads native getFeatureFlagResult().enabled (posthog_flutter_io.dart), so it follows the native PRs. PostHogFeatureFlagsLoaded already drops "" on every platform (feature_flags_loaded.dart).
  • Windows and Linux (Dart core): isFeatureEnabled() returned true and getFeatureFlagResult().enabled was true; both now read "" as disabled.
  • Unchanged: getFeatureFlag() still returns "", and $feature_flag_called still reports $feature_flag_response: "".

The changeset and MIGRATION.md now say empty variants read as disabled from isFeatureEnabled() (with or without defaultValue), getFeatureFlagResult().enabled and onFeatureFlags listeners on iOS, macOS, Android, Windows and Linux, as on web.

Blast radius is small: since PostHog/posthog#118803 (2026-10-16) PostHog rejects new "" variant keys, so only flags created earlier through the API, MCP or Terraform can hit this.

Depends on the native 4.0 changes:

$active_feature_flags now leaves a "" flag out on desktop events too (iOS, macOS and Android get this from the native PRs), while $feature/<key> still carries the raw "", as in posthog-js (browser).

💚 How did you test it?

  • Added "an empty variant reads as disabled" to test/core/feature_flags_test.dart (isFeatureEnabled, getFeatureFlagResult enabled/variant, featureFlagsLoaded().flags). It fails on the pre-fix core (Expected: false, Actual: true) and passes with the fix.
  • flutter test test/core/feature_flags_test.dart test/posthog_flutter_desktop_test.dart test/posthog_flutter_io_test.dart test/feature_flags_listeners_test.dart: 352 passed. dart format and flutter analyze clean.
  • Extended $active_feature_flags "lists only enabled flags" with a "" variant flag: fails on the pre-fix core (Actual: [on-flag, variant-flag, empty-variant-flag]), passes with the fix. Full flutter test: 1558 passed; flutter analyze shows only an existing info in test/posthog_observer_test.dart.
  • Not run on a device: the iOS/Android behavior comes from the native PRs, which aren't released yet.

📝 Checklist

  • All commits are signed (see Commit signing)
  • 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 (edited the existing 6.0 changeset)

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Claude Code made the change across iOS, Android, Flutter and sdk-specs at Anna's direction. Checking the desktop Dart core turned up the same split there, so it's fixed here too.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

posthog-flutter Compliance Report

Date: 2026-11-08T17:36:15.298181+00:00
Duration: 371447ms

✅ All Tests Passed!

232/232 tests passed


Capture_V1 Tests

✅ 105/105 tests passed

View Details
Test Status Duration
Endpoint And Method.Targets V1 Endpoint ✅ 223ms
Endpoint And Method.Does Not Use Legacy Endpoints ✅ 129ms
Required Headers.Has Authorization Bearer Header ✅ 123ms
Required Headers.Has Content Type Json ✅ 126ms
Required Headers.Has Posthog Sdk Info Format ✅ 123ms
Required Headers.Has Posthog Attempt Header ✅ 121ms
Required Headers.Has Posthog Request Id ✅ 123ms
Required Headers.Has Posthog Request Timestamp ✅ 122ms
Required Headers.Has User Agent ✅ 123ms
Body Format.Body Has Created At And Batch ✅ 120ms
Body Format.No Api Key In Body ✅ 120ms
Body Format.No Sent At In Body ✅ 120ms
Event Format.Event Has Required Root Fields ✅ 120ms
Event Format.Event Uuid Is Valid ✅ 121ms
Event Format.Event Timestamp Is Rfc3339 ✅ 121ms
Event Format.Non Utc Event Timestamp Is Converted To Utc ✅ 122ms
Event Format.Distinct Id Is String ✅ 124ms
Event Format.Distinct Id At Root Not Properties ✅ 123ms
Event Format.Custom Properties Preserved ✅ 120ms
Event Format.Set Properties Preserved ✅ 124ms
Event Format.Set Once Properties Preserved ✅ 121ms
Event Format.Groups Properties Preserved ✅ 120ms
Event Format.Sdk Generates Uuid If Not Provided ✅ 120ms
Event Format.Event Has Required Root Fields Batch ✅ 127ms
Event Format.Event Uuid Is Valid Batch ✅ 129ms
Event Format.Event Timestamp Is Rfc3339 Batch ✅ 128ms
Event Format.Distinct Id Is String Batch ✅ 126ms
Event Format.Distinct Id At Root Not Properties Batch ✅ 127ms
Event Format.Custom Properties Preserved Batch ✅ 131ms
Event Format.Set Properties Preserved Batch ✅ 128ms
Event Format.Set Once Properties Preserved Batch ✅ 129ms
Event Format.Groups Properties Preserved Batch ✅ 128ms
Event Format.Sdk Generates Uuid If Not Provided Batch ✅ 131ms
Batch Behavior.Multiple Events In Single Batch ✅ 136ms
Batch Behavior.Batch Envelope Smoke ✅ 134ms
Batch Behavior.Flush With No Events Sends Nothing ✅ 111ms
Batch Behavior.Flush At Triggers Batch ✅ 1019ms
Batch Behavior.Created At Reflects Batch Creation Time ✅ 122ms
Deduplication.Generates Unique Uuids ✅ 132ms
Deduplication.Different Events Same Content Different Uuids ✅ 122ms
Deduplication.Preserves Uuid On Retry ✅ 5129ms
Deduplication.Preserves Timestamp On Retry ✅ 5125ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5136ms
Deduplication.No Duplicate Events In Batch ✅ 132ms
Header Behavior On Retry.Attempt Header Starts At One ✅ 117ms
Header Behavior On Retry.Attempt Header Increments On Retry ✅ 10129ms
Header Behavior On Retry.Request Id Preserved On Retry ✅ 5125ms
Header Behavior On Retry.Different Requests Have Different Request Ids ✅ 2236ms
Header Behavior On Retry.Request Timestamp Changes On Retry ✅ 5126ms
Response Format Validation.Success Response Has Uuid Keyed Results ✅ 119ms
Response Format Validation.Success Response Has Ok For Each Event ✅ 124ms
Response Format Validation.Success No Retry After When All Ok ✅ 122ms
Response Format Validation.Success Retry After Present When Retry Events ✅ 123ms
Response Format Validation.Success No Retry After When Drop Only ✅ 125ms
Response Format Validation.Response Echoes Request Id ✅ 118ms
Retry Behavior.Retries On 408 ✅ 5125ms
Retry Behavior.Retries On 500 ✅ 5125ms
Retry Behavior.Retries On 503 ✅ 5125ms
Retry Behavior.Retries On 504 ✅ 5123ms
Retry Behavior.Retries On 404 ✅ 5131ms
Retry Behavior.Retries On 429 ✅ 5125ms
Retry Behavior.Retries On 502 ✅ 5124ms
Retry Behavior.Retryable Errors Have Retry After ✅ 2122ms
Retry Behavior.Respects Retry After On Retryable Error ✅ 8127ms
Retry Behavior.Does Not Retry On 400 ✅ 2122ms
Retry Behavior.Does Not Retry On 401 ✅ 2121ms
Retry Behavior.Does Not Retry On 402 ✅ 2122ms
Retry Behavior.Does Not Retry On 413 ✅ 2122ms
Retry Behavior.Does Not Retry On 415 ✅ 2121ms
Retry Behavior.Non Retryable Errors Have No Retry After ✅ 2122ms
Retry Behavior.Implements Backoff ✅ 12132ms
Partial Batch Handling.Handles 200 Full Success ✅ 2122ms
Partial Batch Handling.Handles 200 With All Ok ✅ 3127ms
Partial Batch Handling.Does Not Retry Dropped Events ✅ 3125ms
Partial Batch Handling.Does Not Retry Warning Events ✅ 3124ms
Partial Batch Handling.Prunes Ok Events On Partial Retry ✅ 5128ms
Partial Batch Handling.Prunes Dropped Events On Partial Retry ✅ 5127ms
Partial Batch Handling.Retries Only Retry Events From Partial ✅ 5130ms
Partial Batch Handling.Partial Retry Preserves Uuids ✅ 5128ms
Partial Batch Handling.Partial Retry Attempt Header Increments ✅ 5128ms
Partial Batch Handling.Partial Retry Request Id Preserved ✅ 5127ms
Partial Batch Handling.Respects Retry After On Partial ✅ 5131ms
Partial Batch Handling.Unknown Result Treated As Terminal ✅ 3123ms
Partial Batch Handling.Mixed Ok Drop Warning No Retry ✅ 3132ms
Compression.Sends Gzip Content Encoding ✅ 121ms
Compression.No Content Encoding When Disabled ✅ 119ms
Compression.Compressed Body Is Decompressible ✅ 116ms
Error Handling.Does Not Retry On Unknown 4Xx ✅ 2120ms
Event Options.Cookieless Mode Override ✅ 118ms
Event Options.Disable Skew Correction Override ✅ 119ms
Event Options.Process Person Profile Override ✅ 117ms
Event Options.Product Tour Id Override ✅ 117ms
Event Options.Unset Options Omitted ✅ 119ms
Event Options.Options Override In Batch ✅ 123ms
Event Options.Unknown Option Passes Through ✅ 119ms
Event Options.Option Value Is Not Converted ✅ 117ms
Event Options.Lenient Bool Option Values Are Not Converted ✅ 118ms
Event Options.Option Wins Over Legacy Property ✅ 117ms
Event Options.Legacy Property Fills Unset Option ✅ 117ms
Event Options.Null Option Falls Back To Legacy Property ✅ 118ms
Property Layers.Caller Property Beats Registered Property ✅ 118ms
Property Layers.Before Send Runs Last ✅ 118ms
Fail Open.Empty Api Key Sends Nothing ✅ 1112ms
Fail Open.Personal Api Key Sends Nothing ✅ 1112ms
Geoip And Historical Migration.Historical Migration Absent By Default ✅ 117ms

Capture_Ai_V1 Tests

✅ 110/110 tests passed

View Details
Test Status Duration
Endpoint And Method.Targets V1 Endpoint ✅ 120ms
Endpoint And Method.Does Not Use Legacy Endpoints ✅ 117ms
Endpoint And Method.Capture Does Not Reroute Ai Named Events ✅ 118ms
Endpoint And Method.Capture Ai Does Not Reroute Non Ai Events ✅ 117ms
Lane Isolation.Failing Ai Lane Does Not Delay Analytics ✅ 5133ms
Required Headers.Has Authorization Bearer Header ✅ 118ms
Required Headers.Has Content Type Json ✅ 121ms
Required Headers.Has Posthog Sdk Info Format ✅ 124ms
Required Headers.Has Posthog Attempt Header ✅ 117ms
Required Headers.Has Posthog Request Id ✅ 118ms
Required Headers.Has Posthog Request Timestamp ✅ 117ms
Required Headers.Has User Agent ✅ 118ms
Body Format.Body Has Created At And Batch ✅ 117ms
Body Format.No Api Key In Body ✅ 118ms
Body Format.No Sent At In Body ✅ 118ms
Event Format.Event Has Required Root Fields ✅ 118ms
Event Format.Event Uuid Is Valid ✅ 117ms
Event Format.Event Timestamp Is Rfc3339 ✅ 117ms
Event Format.Non Utc Event Timestamp Is Converted To Utc ✅ 118ms
Event Format.Distinct Id Is String ✅ 136ms
Event Format.Distinct Id At Root Not Properties ✅ 117ms
Event Format.Custom Properties Preserved ✅ 117ms
Event Format.Set Properties Preserved ✅ 117ms
Event Format.Set Once Properties Preserved ✅ 118ms
Event Format.Groups Properties Preserved ✅ 117ms
Event Format.Sdk Generates Uuid If Not Provided ✅ 118ms
Event Format.Event Has Required Root Fields Batch ✅ 123ms
Event Format.Event Uuid Is Valid Batch ✅ 123ms
Event Format.Event Timestamp Is Rfc3339 Batch ✅ 122ms
Event Format.Distinct Id Is String Batch ✅ 124ms
Event Format.Distinct Id At Root Not Properties Batch ✅ 122ms
Event Format.Custom Properties Preserved Batch ✅ 122ms
Event Format.Set Properties Preserved Batch ✅ 124ms
Event Format.Set Once Properties Preserved Batch ✅ 140ms
Event Format.Groups Properties Preserved Batch ✅ 123ms
Event Format.Sdk Generates Uuid If Not Provided Batch ✅ 123ms
Batch Behavior.Multiple Events In Single Batch ✅ 129ms
Batch Behavior.Batch Envelope Smoke ✅ 129ms
Batch Behavior.Flush At Triggers Batch ✅ 1017ms
Batch Behavior.Created At Reflects Batch Creation Time ✅ 117ms
Deduplication.Generates Unique Uuids ✅ 130ms
Deduplication.Different Events Same Content Different Uuids ✅ 121ms
Deduplication.Preserves Uuid On Retry ✅ 5119ms
Deduplication.Preserves Timestamp On Retry ✅ 5124ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5129ms
Deduplication.No Duplicate Events In Batch ✅ 132ms
Header Behavior On Retry.Attempt Header Starts At One ✅ 122ms
Header Behavior On Retry.Attempt Header Increments On Retry ✅ 10129ms
Header Behavior On Retry.Request Id Preserved On Retry ✅ 5124ms
Header Behavior On Retry.Different Requests Have Different Request Ids ✅ 2232ms
Header Behavior On Retry.Request Timestamp Changes On Retry ✅ 5125ms
Response Format Validation.Success Response Has Uuid Keyed Results ✅ 117ms
Response Format Validation.Success Response Has Ok For Each Event ✅ 123ms
Response Format Validation.Success No Retry After When All Ok ✅ 120ms
Response Format Validation.Success Retry After Present When Retry Events ✅ 121ms
Response Format Validation.Success No Retry After When Drop Only ✅ 122ms
Response Format Validation.Response Echoes Request Id ✅ 117ms
Retry Behavior.Retries On 408 ✅ 5123ms
Retry Behavior.Retries On 500 ✅ 5118ms
Retry Behavior.Retries On 503 ✅ 5124ms
Retry Behavior.Retries On 504 ✅ 5123ms
Retry Behavior.Retries On 404 ✅ 5123ms
Retry Behavior.Retries On 429 ✅ 5124ms
Retry Behavior.Retries On 502 ✅ 5123ms
Retry Behavior.Retryable Errors Have Retry After ✅ 2121ms
Retry Behavior.Respects Retry After On Retryable Error ✅ 8127ms
Retry Behavior.Does Not Retry On 400 ✅ 2122ms
Retry Behavior.Does Not Retry On 401 ✅ 2120ms
Retry Behavior.Does Not Retry On 402 ✅ 2123ms
Retry Behavior.Does Not Retry On 413 ✅ 2120ms
Retry Behavior.Does Not Retry On 415 ✅ 2121ms
Retry Behavior.Non Retryable Errors Have No Retry After ✅ 2120ms
Retry Behavior.Implements Backoff ✅ 12132ms
Partial Batch Handling.Handles 200 Full Success ✅ 2119ms
Partial Batch Handling.Handles 200 With All Ok ✅ 3127ms
Partial Batch Handling.Does Not Retry Dropped Events ✅ 3124ms
Partial Batch Handling.Does Not Retry Warning Events ✅ 3124ms
Partial Batch Handling.Prunes Ok Events On Partial Retry ✅ 5126ms
Partial Batch Handling.Prunes Dropped Events On Partial Retry ✅ 5131ms
Partial Batch Handling.Retries Only Retry Events From Partial ✅ 5131ms
Partial Batch Handling.Partial Retry Preserves Uuids ✅ 5122ms
Partial Batch Handling.Partial Retry Attempt Header Increments ✅ 5132ms
Partial Batch Handling.Partial Retry Request Id Preserved ✅ 5127ms
Partial Batch Handling.Respects Retry After On Partial ✅ 5129ms
Partial Batch Handling.Unknown Result Treated As Terminal ✅ 3125ms
Partial Batch Handling.Mixed Ok Drop Warning No Retry ✅ 3129ms
Compression.Sends Gzip Content Encoding ✅ 118ms
Compression.No Content Encoding When Disabled ✅ 117ms
Compression.Compressed Body Is Decompressible ✅ 117ms
Error Handling.Does Not Retry On Unknown 4Xx ✅ 2120ms
Event Options.Cookieless Mode Override ✅ 129ms
Event Options.Disable Skew Correction Override ✅ 119ms
Event Options.Process Person Profile Override ✅ 118ms
Event Options.Product Tour Id Override ✅ 118ms
Event Options.Unset Options Omitted ✅ 117ms
Event Options.Options Override In Batch ✅ 123ms
Event Options.Unknown Option Passes Through ✅ 117ms
Event Options.Option Value Is Not Converted ✅ 117ms
Event Options.Lenient Bool Option Values Are Not Converted ✅ 117ms
Event Options.Option Wins Over Legacy Property ✅ 118ms
Event Options.Legacy Property Fills Unset Option ✅ 117ms
Event Options.Null Option Falls Back To Legacy Property ✅ 120ms
Event Size.Event Over Ceiling Is Not Sent ✅ 4178ms
Event Size.Over Ceiling Drop Is Reported Or Logged Without Content ✅ 2199ms
Event Size.Batches Close Before Byte Target ✅ 6251ms
Geoip And Historical Migration.Historical Migration Absent By Default ✅ 119ms
Property Layers.Caller Property Beats Registered Property ✅ 117ms
Property Layers.Before Send Runs Last ✅ 116ms
Fail Open.Empty Api Key Sends Nothing ✅ 1113ms
Fail Open.Personal Api Key Sends Nothing ✅ 1113ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 24ms
Request Payload.Flags Request Uses V2 Query Param ✅ 19ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 12ms
Request Payload.Flags Request Omits Authorization Header ✅ 14ms
Request Payload.Token In Flags Body Matches Init ✅ 13ms
Request Payload.Groups Round Trip ✅ 13ms
Request Payload.Groups Default To Empty Object ✅ 13ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 13ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 13ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 14ms
Request Lifecycle.No Flags Request On Init Alone ✅ 5ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 116ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 22ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 15ms
Retry Behavior.Retries Flags On 502 ✅ 320ms
Retry Behavior.Retries Flags On 504 ✅ 320ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 123ms

…_flags

Desktop core change; iOS, macOS and Android pick it up from the native SDKs.
$feature/<key> still carries the raw "" value, matching posthog-js.
@turnipdabeets
turnipdabeets merged commit 0046369 into chore/v6-posthog-ios-4 Nov 9, 2026
20 of 23 checks passed
@turnipdabeets
turnipdabeets deleted the fix/v6-empty-variant-disabled branch November 9, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants