Skip to content

docs(flags): read an empty variant as disabled in get-feature-flag-result - #193

Merged
turnipdabeets merged 3 commits into
mainfrom
fix/empty-variant-disabled
Nov 10, 2026
Merged

turnipdabeets merged 3 commits into
mainfrom
fix/empty-variant-disabled

Conversation

@turnipdabeets

@turnipdabeets turnipdabeets commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Say what a flag whose variant is the empty string "" reads as: disabled.

  • get-feature-flag-result: new Empty variant reads as disabled requirement (enabled: false, variant: "", and is-feature-enabled returns false), with a @both scenario in the spec and in acceptance/public/get-feature-flag-result.feature. The client and server flow descriptions say the same.
  • is-feature-enabled: the server-side flow says "non-empty variant string → true", matching the client-side flow.
  • Proposal, applied and archived in openspec/changes/archive/2026-11-06-empty-variant-disabled/.

Why

posthog-ios #1001 made isFeatureEnabled return false for "", but getFeatureFlagResult().enabled stayed true there and on Android (review thread). posthog-js (browser) already reads "" as disabled everywhere, so it's named the winner. Since PostHog/posthog#118803 (2026-10-16) PostHog rejects new "" variant keys, so only older flags created through the API, MCP or Terraform can hit this.

SDK PRs:

Notes

  • Server SDKs weren't audited for this case; the scenario is tagged @both because the server-side flow now says the same thing.
  • No acceptance harness implements these steps yet. The "" cell in cached feature flags are: is meant as the empty string; a feature comment says so.
  • openspec validate --all --strict: 69 passed.

🤖 Generated with Claude Code

…ctive_feature_flags

Add on-feature-flags and capture requirements, list posthog-kmp in the
impact, and record the posthog-js core and react-native gap. Also refresh
the Android 4.0 and Flutter 6.0 reload signatures and Flutter 6.0's
per-call sendFeatureFlagEvent option.
@marandaneto
marandaneto requested a review from a team November 9, 2026 12:32
@marandaneto

marandaneto commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

worth checking with flags if this is ok, it was intentionally set as empty

@haacked haacked self-assigned this Nov 9, 2026

@gustavohstrassburger gustavohstrassburger 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.

Approving. I checked how many flags this actually affects: in US there are only 6 flags with a "" variant key (5 active, across 4 projects). Since PostHog/posthog#118803 the API no longer accepts new "" variant keys, so that number can only go down. I haven't checked EU yet.

Given that, I'm fine keeping the behavior this PR specifies. I'd still reach out to those customers so they rename these invalid variants, or at least confirm the change doesn't affect them before the iOS 4.0 / Android 4.0 / Flutter 6.0 majors ship. Low risk, but not zero: /flags still returns enabled: true for these flags, so anyone relying on that today will see it flip to false after upgrading.

…results

Where getAllFeatureFlags() returns flag result objects (iOS 4.0+, Android
4.0+, Flutter 6.0+, KMP), each entry matches getFeatureFlagResult, so a ""
flag stays in the list with enabled: false. Key-to-value maps keep "".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@turnipdabeets
turnipdabeets merged commit 752f899 into main Nov 10, 2026
11 checks passed
@turnipdabeets
turnipdabeets deleted the fix/empty-variant-disabled branch November 10, 2026 17:26
turnipdabeets added a commit that referenced this pull request Nov 10, 2026
…st main 752f899 and #146 d387ab7

Score Capture against the client $lib pair fill #146 now requires
(posthog-android#956), the #193 empty-variant rule and kept null array
elements (#955). Drop the settled compression and $lib conflict notes:
#146 exempts a lane with its own compression setting from the opt-out.
Update line numbers and the MIGRATION.md links, list the out-of-scope
rows later 4.0 and spec changes leave stale, and add note anchors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
turnipdabeets added a commit that referenced this pull request Nov 10, 2026
…52f899 and #146 d387ab7

Capture, Capture Exception and Capture AI move to Pass now that null-valued
properties are omitted (posthog-ios#1047) and the SDK fills its own $lib pair
after the caller's properties (posthog-ios#1050). On Feature Flags moves to
Pass: loads in flight across reset() are dropped (posthog-ios#1048) and empty
variants are left out of listener flags. HTTP Client is scored Pass on its
merits, since #146 now leaves a lane with its own compression setting out of
the whole-client opt-out. Surveys stays Partial only for the popup-delay
eligibility re-check (SHOULD); posthog-ios#1049 fixed the rest.

Opt In meets the new deferred-crash consent requirement (#199); the empty
variant (#193), variant key rename (#203) and job-retry (#205) changes need no
other status change. Line references move to e27cbe4cb.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
turnipdabeets added a commit that referenced this pull request Nov 11, 2026
* docs(compliance): re-audit posthog-android capture rows for 4.0

Re-score Capture, Capture AI, Event Batcher, HTTP Client and Retry Queue
against posthog-android 4.0 (be3ed4b4) and the capture v1 text in #146
and #147. Capture AI now applies to android. Capture, Capture AI and
HTTP Client move to partial on post-beforeSend uuid/options restore and
a caller httpClient's redirect policy; Retry Queue stays a pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): re-check posthog-android 4.0 rows against #146 at 5c26b9b

Re-checks the five capture v1 rows at posthog-android 8b63a135 and #146 at
5c26b9b, which includes #147. #146 now allows the post-hook uuid and options
restore and the redirect rule on a caller's httpClient, so Capture and HTTP
Client stay ✅ and Capture AI scores ✅. Android 4.0 now keeps $lib in
properties (#942). Two #146 conflicts (compression opt-out scope and client
$lib precedence) are recorded as open questions, not scored. Tally 38/16/5/5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): score posthog-android 4.0 rows against #146 as merged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): treat the #146 $lib precedence conflict as resolved for clients

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): re-check posthog-android 4.0 rows at aaa23d39 against main 752f899 and #146 d387ab7

Score Capture against the client $lib pair fill #146 now requires
(posthog-android#956), the #193 empty-variant rule and kept null array
elements (#955). Drop the settled compression and $lib conflict notes:
#146 exempts a lane with its own compression setting from the opt-out.
Update line numbers and the MIGRATION.md links, list the out-of-scope
rows later 4.0 and spec changes leave stale, and add note anchors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): re-audit posthog-android consent, identify, setup, shutdown and lifecycle rows at 4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): re-audit posthog-android remote config, flag cache, reload, flag-called tracker, before-send and logs rows at 4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): re-audit posthog-android flag-read rows at 4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): re-audit posthog-android session replay rows at 4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): re-audit posthog-android surveys and exception rows at 4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): update posthog-android 4.0 totals, scope and README roll-ups

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): re-score posthog-android Alias at 4.0

alias() sets properties.distinct_id since PostHog/posthog-android#819, which
is in aaa23d39, so the row moves from ❌ to ✅ and joins the 4.0 re-audit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): score posthog-android Capture partial for the identity-call guide gap

#146 at 15501ca requires migration guides to show a process_person_profile:
true call option on identity calls. The 4.0 MIGRATION.md doesn't yet;
PostHog/posthog-android#966 adds it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): add the five newer contracts to posthog-android as N/A

Experiment Exposure, AI Datasets, AI Scorers, Offline Evaluations and Emails
join the matrix, matching main's 69 contracts and the iOS 4.0 page. All are
N/A: android requests /flags?v=2 and the other four are server-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): keep posthog-android Capture passing while tracking 4.0

The identity-call migration-guide example #146 requires lands in 4.0 with
PostHog/posthog-android#966, which targets the release branch. This audit
tracks 4.0, so a pending release-branch PR is noted rather than scored as a
gap. Undoes the Capture downgrade from cfc72e1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): make the 69-contract README note SDK-neutral

The same paragraph lands from both the iOS and Android 4.0 audits, so it
merges cleanly in either order and stays true after both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): point posthog-android Identify remediation at #967

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
turnipdabeets added a commit that referenced this pull request Nov 11, 2026
* chore(compliance): audit posthog-ios 4.0

Re-audit all 64 posthog-ios rows at 3460e534a (4.0 release branch)
against sdk-specs dd11a6b, add the five rows added to the README table
since the last audit, and update the iOS roll-up and gap entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(compliance): score posthog-ios HTTP Client partial

compression = .none leaves captureAi requests gzipped through the
separate captureAiCompression setting, so iOS 4.0 has no whole-client
compression opt-out. Roll-up becomes 40/17/1/11 (58%), 18 gaps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): list iOS capture v1 retries as matching #146

Draft #146 already permits the client-SDK durable queue, unlimited
attempts, 1-30 s backoff and multi-event 413 halving for iOS 4.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(compliance): score posthog-ios Session Replay Privacy partial

SwiftUI's postHogMask() wins over an enclosing postHogNoMask(), the
reverse of the spec's unmask-over-mask precedence. It over-masks rather
than leaks. Roll-up becomes 39/18/1/11 (57%), 19 gaps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): re-check posthog-ios 4.0 at 1f91c5b2 against main and #146

Re-checks the iOS audit at 1f91c5b2 and sdk-specs 8306698. #1036 keeps
$lib/$lib_version, which now matches #146. Opt In meets the #138 replay
restart requirements. Surveys gains a #102 gap: setSurveySeen moves the
last-seen date on submit and dismiss. HTTP Client goes back to ✅: its
compression gap comes from conflicting http-client and capture-ai rules in
#146, raised on that PR. Tally 40/17/1/11.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): score posthog-ios 4.0 capture v1 rows against #146 as merged

Scores Capture, Capture AI, Before Send Hook, Event Batcher, HTTP Client and
Retry Queue against #146 at 5c26b9b, treated as merged. The capture v1 wire,
transport and retry policy conform. Capture AI applies to iOS 4.0 and is 🟡
for the same NSNull serialization gap as Capture; starting the AI queue at
setup is what #146 asks of client SDKs. Tally 40/18/1/10.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): treat the #146 $lib precedence conflict as resolved for clients

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): re-check posthog-ios 4.0 at e27cbe4cb against main 752f899 and #146 d387ab7

Capture, Capture Exception and Capture AI move to Pass now that null-valued
properties are omitted (posthog-ios#1047) and the SDK fills its own $lib pair
after the caller's properties (posthog-ios#1050). On Feature Flags moves to
Pass: loads in flight across reset() are dropped (posthog-ios#1048) and empty
variants are left out of listener flags. HTTP Client is scored Pass on its
merits, since #146 now leaves a lane with its own compression setting out of
the whole-client opt-out. Surveys stays Partial only for the popup-delay
eligibility re-check (SHOULD); posthog-ios#1049 fixed the rest.

Opt In meets the new deferred-crash consent requirement (#199); the empty
variant (#193), variant key rename (#203) and job-retry (#205) changes need no
other status change. Line references move to e27cbe4cb.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): score posthog-ios Surveys as pass

The popup-delay and wait-period SHALLs are met since posthog-ios#1049. The
one unmet requirement is a SHOULD (re-check eligibility when the delay
elapses), which is recorded but not scored. Totals become 45/13/1/10.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): score iOS Exception Steps partial on NSNull normalization

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): score posthog-ios Capture partial until the migration guide shows the identity call option

#146 at 15501ca requires migration guides to show a per-call
process_person_profile: true on identity calls; MIGRATION.md at e27cbe4cb
lacks it until PostHog/posthog-ios#1053 merges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): note the README's 69 contracts and the iOS row's 69-row basis

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): keep posthog-ios Capture passing while tracking 4.0

The identity-call migration-guide example #146 requires lands in 4.0 with
PostHog/posthog-ios#1053, which targets the release branch. This audit
tracks 4.0, so a pending release-branch PR is noted rather than scored as a
gap. Reverts 85d4acd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(compliance): make the 69-contract README note SDK-neutral

The same paragraph lands from both the iOS and Android 4.0 audits, so it
merges cleanly in either order and stays true after both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
andehen added a commit to PostHog/posthog-sdk-test-harness that referenced this pull request Nov 12, 2026
The pin move brings in PostHog/sdk-specs#193, which adds "Listener
does not receive an empty variant flag" to
acceptance/public/on-feature-flags.feature. Like the other four, it is
not applicable to the server profile. The SDK_V2_SPECS-gated boundary
test counted four.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants