fix(flags): filter out failed flags on merge#96
Merged
matheus-vb merged 4 commits intomainfrom Feb 6, 2026
Merged
Conversation
rafaeelaudibert
approved these changes
Feb 6, 2026
Member
rafaeelaudibert
left a comment
There was a problem hiding this comment.
Look at the README to understand how to release a new version here, you'll need to add an entry to CHANGELOG.md, update VERSION and add a release label to this PR
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.
Problem
When the
/flagsendpoint fails to evaluate a specific flag due to a transient error (e.g. DB timeout), the server returns that flag withenabled: falseand a newfailed: truefield. Previously, the SDK had no way to distinguish a legitimately disabled flag from one that failed to evaluate. This caused two issues:get_flags: Failed flags were included infeatureFlagsandfeatureFlagPayloadswith their incorrectenabled: falsevalue, which callers would consume as a real result.get_all_flags_and_payloads: When a server fallback was triggered (e.g. because one flag required server evaluation), the server response fully replaced all locally-evaluated results. If the server had a transient error on a flag that was successfully evaluated locally astrue, the local value was lost.Combined, a flag that was correctly
truecould flip tofalseduring a transient server error.See PostHog/posthog#46204.
Changes
lib/posthog/feature_flag.rbfailedfield from the v4 flags response intoFeatureFlag.lib/posthog/feature_flags.rbget_flags: Filter out flags withfailed == truebefore buildingfeatureFlagsandfeatureFlagPayloads. Failed flags remain inflags_response[:flags]for reference but are excluded from the hashes that callers use for flag values and payloads.get_all_flags_and_payloads: WhenerrorsWhileComputingFlagsis true, merge server results into locally-evaluated results instead of replacing them. Successful server flags overwrite local values (they are more current), but flags that failed server-side evaluation (already filtered out byget_flags) are absent from the merge, so the locally-evaluated values are preserved. When there are no errors, the server response fully replaces local results as before. This matches the pattern used by the JS and Kotlin SDKs.Tests
spec/posthog/feature_flag_error_spec.rb— 6 new test cases:enabled: trueis excluded: A flag withfailed: trueandenabled: truereturnsfalseand reportsflag_missing. Without filtering, it would returntrue.failed: trueandvariant: 'test-variant'returnsfalse. Without filtering, it would return the variant.get_all_flagsexcludes failed flags: Failed flags do not appear in the hash returned byget_all_flags, while non-failed flags are present.true. A second flag requires server evaluation, triggering fallback. The server returns the first flag asfailed: true, enabled: false. The locally-evaluatedtrueis preserved, not overwritten.failedfield (e.g. from an older server) are included normally.failed: falsereturn their values as expected.