Repository navigation
docs(flags): honor the feature flag event opt-out on snapshot reads - #111
Merged
Merged
Conversation
7 of 23 tasks
Snapshot isEnabled()/getFlag() reads now take an optional per-read sendFeatureFlagEvents option, resolved as per-read value, then the SDK's client-level default where one exists, then send. A read that does not send emits no $feature_flag_called (including flag_missing), leaves the dedupe tracker untouched, and still counts for onlyAccessed(). evaluateFlags() takes no per-snapshot option, and the supersession map now carries the per-call option across to the snapshot read.
andehen
force-pushed
the
docs/snapshot-flag-event-opt-out
branch
from
October 13, 2026 07:39
3b1864e to
114246c
Compare
andehen
marked this pull request as ready for review
October 13, 2026 07:41
dustinbyrne
approved these changes
Oct 20, 2026
…ent-opt-out # Conflicts: # acceptance/public/evaluate-flags.feature # openspec/specs/evaluate-flags/spec.md
The per-call option requirement said value and result APIs default to sending the event. Read literally, an SDK could declare the option as `true` and override a disabled client-level default. An omitted option now falls back to the client-level default where the SDK has one. Add scenarios for an enabled client default with a per-read `false`, and for a suppressed snapshot read followed by a single-flag getter.
SDKs without a client-level default can default the per-call option to true. #5252 is still closed, so the migration plan now says to reopen it or implement an equivalent.
This was referenced Oct 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
evaluateFlags()is the preferred server API, and the single-flag getters it supersedes (getFeatureFlag,isFeatureEnabled,getFeatureFlagResult, …) now log deprecation warnings that tell users to migrate to it. Those getters let the caller control$feature_flag_called: every server SDK takes a per-call option, and some SDKs also have a client-level default. Theevaluate-flagscontract mentions neither control, so snapshot reads honor neither.send_feature_flag_events=Falsegets every event back after migrating. A posthog-node client created withsendFeatureFlagEvent: falsegets them back from snapshot reads.getFeatureFlag(key, id, { sendFeatureFlagEvents: true }), has no snapshot equivalent. Once snapshot reads honor the client default, those experiments would see no exposures.evaluateFlags(), and a second/flagsrequest, to mix them.onlyAccessed(), because the code did branch on that flag.What changed
New requirement Snapshot feature flag event control in
evaluate-flags:sendFeatureFlagEventsoption, in platform casing (send_feature_flag_events), with the same meaning as the per-call option on the server getters. Payload, key enumeration and filters take no option.true. This is the order the legacy getters already use.false, the read emits no$feature_flag_called, includingflag_missing. It doesn't consult or update the dedupe tracker, so a later opted-in read of the same key still emits. It still marks the key as accessed foronlyAccessed().evaluateFlags()takes no per-snapshot option.Amended requirements:
getFeatureFlag(key, id, { sendFeatureFlagEvents: x })→evaluateFlags(id).getFlag(key, { sendFeatureFlagEvents: x }). The "not side-effect-preserving" note now says exposure-event control is preserved. The other documented differences (bulk maps, payload silence, Go's legacy payload exposure) are unchanged.flag_missingreporting now defer to the new control. Access marking is unchanged.Unchanged: creating a snapshot emits nothing, payload reads stay silent, and a read with no option anywhere still emits.
acceptance/public/evaluate-flags.featureadds six scenarios that mirror the spec scenarios. They use the request-local snapshot read steps from #107: the per-read option goes in a read'soptionsassend_feature_flag_events, and the client-level default goes in the setup configuration assend_feature_flag_event. They are tagged@server, not@sdk:server, because no SDK has run them end to end through the shared harness yet. The two client-default scenarios also carry@requires:flag_event_default, because only some SDKs have a client-level default.Proposed, applied and archived on this branch under
openspec/changes/archive/2026-10-08-honor-snapshot-flag-event-opt-out/. The design doc records the SDK survey and a migration note: an SDK whose snapshot reads ignored its client-level default until now should say in its changelog that callers who relied on snapshot reads for exposures while that default was off must now opt those reads in.Validation
openspec validate --specs --strict(CLI 1.4.1, as in CI): 64 passed, 0 failed.@cucumber/gherkin: 58 scenario definitions, up from 52 onmain.git diff --checkis clean.These are specification checks. No SDK conformance was executed.
Evidence
Server SDK survey of default branches on 2026-10-08:
sendFeatureFlagEventsendFeatureFlagEventsPostHogConfig.sendFeatureFlagEventsendFeatureFlagEventongetFeatureFlagResultsend_feature_flag_eventssend_feature_flag_events:$sendFeatureFlagEventsFeatureFlagPayload.SendFeatureFlagEventsFeatureFlagOptions.SendFeatureFlagEventssend_event:onget_feature_flag_result_getFeatureFlagEvaluationsHostforwards every snapshot read to_captureFlagCalledEventIfNeededwithout checking the client option. The legacy getters resolveoptions?.sendFeatureFlagEvents ?? this.options.sendFeatureFlagEvent ?? true(getFeatureFlag,isFeatureEnabled). The snapshot accessorsisEnabledandgetFlagtake no event option.isEnabled()andgetFlag()honorsendFeatureFlagEvent, and adds a per-readsendFeatureFlagEventsoption to both, with the precedence, dedupe and access rules in this PR. A separate commit makes the OpenFeature Node provider leavesendFeatureFlagEventsunset unless its caller sets it.evaluate_flagstakes no event control. Neither dois_enabledandget_flag. The client has no client-level option.sendFeatureFlagEventisfalse, and the legacy path uses the same precedence. It is the existing reference for the client-default half of this change.Considered alternatives
evaluateFlags(). Rejected. Mixing experiment and operational flags in one request would need two evaluations and two/flagsrequests, which defeats the single-evaluation purpose of the API. This is the same reasoning that put the runtime filter on the snapshot.onlyAccessed()would drop flags the code branched on.Related
test: observe remote feature flag snapshot reads) has landed, and this PR is rebased onto it.docs(flags): name the per-call feature flag event option) names the optionsendFeatureFlagEventson server SDKs, and this PR uses that name for the per-read option. There is no file overlap, so either can land first. Two notes for docs(flags): name the per-call feature flag event option #109:sendFeatureFlagEvent) and posthog-elixir's issend_event, so the plural server rule doesn't yet match every server SDK.Follow-ups
sendFeatureFlagEventsoption onisEnabled()/getFlag(), and its changeset tells users who relied on snapshot reads for exposures whilesendFeatureFlagEventwasfalseto opt those reads in.send_feature_flag_eventskeyword tois_enabled()/get_flag().packages/openfeature-node-providerin posthog-js defaults itssendFeatureFlagEventstotrue(provider.ts:78) and passes it on every call (provider.ts:154). That overrides a client created withsendFeatureFlagEvent: false. The provider should leave the option unset unless its caller sets it. #5252 makes that change in a separate commit.send_feature_flag_eventsonis_enabledandget_flagreads, andsend_feature_flag_eventin the setup configuration, to the SDK's public options. Declareflag_event_defaultfor SDKs with a client-level default. Once the six scenarios pass end to end against an SDK with the option, change their tag to@sdk:server.🤖 Generated with Claude Code