From 26c905f52856d49896b82eb8939b5147bda4be3d Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Tue, 25 Aug 2026 12:58:45 +0200 Subject: [PATCH] fix(flags): handle empty evaluation key scopes --- .changeset/empty-scopes-return.md | 5 ++++ lib/posthog/client.rb | 13 ++++++--- spec/posthog/feature_flag_evaluations_spec.rb | 28 +++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 .changeset/empty-scopes-return.md diff --git a/.changeset/empty-scopes-return.md b/.changeset/empty-scopes-return.md new file mode 100644 index 0000000..c9b4fc4 --- /dev/null +++ b/.changeset/empty-scopes-return.md @@ -0,0 +1,5 @@ +--- +'posthog-ruby': patch +--- + +Return an empty feature flag snapshot without evaluation when `flag_keys` is empty. diff --git a/lib/posthog/client.rb b/lib/posthog/client.rb index 52c3ddf..7d8cd54 100644 --- a/lib/posthog/client.rb +++ b/lib/posthog/client.rb @@ -607,10 +607,9 @@ def get_feature_flag_result( # @param [Boolean] only_evaluate_locally Skip the remote /flags call entirely # @param [Boolean, nil] disable_geoip When true, disables GeoIP lookup for remote evaluation and stamps captured # access events. - # @param [Array] flag_keys When set, scopes the underlying /flags - # request to only these flag keys (sent as `flag_keys_to_evaluate`). - # Distinct from {FeatureFlagEvaluations#only}, which filters the - # already-fetched snapshot in memory. + # @param [Array, nil] flag_keys When set, scopes evaluation to only these flag keys. + # An empty array returns an empty snapshot without evaluating flags; +nil+ evaluates all flags. + # Distinct from {FeatureFlagEvaluations#only}, which filters the already-fetched snapshot in memory. # @return [PostHog::FeatureFlagEvaluations] def evaluate_flags( distinct_id, @@ -629,6 +628,12 @@ def evaluate_flags( return FeatureFlagEvaluations.new(host: host, distinct_id: distinct_id, flags: {}, groups: groups) if @disabled + if flag_keys && flag_keys.empty? + return FeatureFlagEvaluations.new( + host: host, distinct_id: distinct_id, flags: {}, groups: groups, disable_geoip: disable_geoip + ) + end + person_properties, group_properties = add_local_person_and_group_properties( groups, person_properties, group_properties ) diff --git a/spec/posthog/feature_flag_evaluations_spec.rb b/spec/posthog/feature_flag_evaluations_spec.rb index 63c0e0a..d37f6fa 100644 --- a/spec/posthog/feature_flag_evaluations_spec.rb +++ b/spec/posthog/feature_flag_evaluations_spec.rb @@ -182,6 +182,34 @@ def capture_stderr ) end + it 'returns an empty snapshot for empty flag_keys without poller or remote work' do + stub_flags(flags_response) + poller = client.instance_variable_get(:@feature_flags_poller) + expect(poller).not_to receive(:load_feature_flags) + expect(poller).not_to receive(:feature_flags_by_key) + expect(poller).not_to receive(:_compute_flag_locally) + expect(poller).not_to receive(:get_flags) + + snapshot = client.evaluate_flags('user-1', flag_keys: []) + + expect(snapshot).to be_a(FeatureFlagEvaluations) + expect(snapshot.keys).to eq([]) + expect(snapshot.enabled?('missing-flag')).to be(false) + expect(snapshot.get_flag('missing-flag')).to be_nil + + events = drain_messages(client).select { |message| message[:event] == '$feature_flag_called' } + expect(events.length).to eq(1) + expect(events.first[:properties]['$feature_flag_error']).to eq('flag_missing') + expect(WebMock).not_to have_requested(:post, FLAGS_ENDPOINT) + end + + it 'treats nil flag_keys as an unscoped evaluation' do + stub_flags(flags_response) + snapshot = client.evaluate_flags('user-1', flag_keys: nil) + expect(snapshot.keys).to match_array(%w[variant-flag boolean-flag disabled-flag]) + expect(WebMock).to have_requested(:post, FLAGS_ENDPOINT).once + end + it 'returns a usable empty snapshot for empty distinct_id and does not call /flags' do stub_flags(flags_response) snapshot = client.evaluate_flags('')