Skip to content

fix(analytics): surface dropped exposure when distinct_id missing from context (SDK-84)#154

Open
tylerjroach wants to merge 1 commit into
masterfrom
fix/sdk-84-warn-on-silent-exposure-drop
Open

fix(analytics): surface dropped exposure when distinct_id missing from context (SDK-84)#154
tylerjroach wants to merge 1 commit into
masterfrom
fix/sdk-84-warn-on-silent-exposure-drop

Conversation

@tylerjroach

Copy link
Copy Markdown
Contributor

Summary

When a flag uses a non-default Variant Assignment Key (e.g. device_id) and the evaluation context doesn't include distinct_id, local eval succeeds — flag definitions are cached, bucketing only needs the configured key — but track_exposure_event then silently returns because it can only attribute the exposure event to a distinct_id. Caller gets the right flag value with no signal that analytics were dropped.

Pass a Mixpanel::MixpanelError to @error_handler (consistent with how the rest of the SDK surfaces problems) so the drop becomes visible to anyone who's configured a real handler.

Context

Linear: SDK-84. Only Ruby and Java are actually affected of the five SDKs the audit listed — Python / Go / Node already log when this happens.

This bug only affects local evaluation. Remote eval requires distinct_id at the eval step itself, so it errors out before reaching exposure tracking.

Behavior when distinct_id IS present (alongside the bucketing key) is unchanged — exposure still fires correctly attributed to the user.

Test plan

  • Existing track_exposure_event test still passes
  • New test asserts MixpanelError is reported through error_handler when the context has device_id but no distinct_id
  • Full flags spec suite (84 examples) passes

🤖 Generated with Claude Code

…m context

When a flag uses a non-default Variant Assignment Key (e.g. device_id),
local evaluation succeeds via that key independently of whether
distinct_id is present. Exposure tracking, however, requires
distinct_id to attribute the event to the user. Previously the SDK
silently returned from track_exposure_event with no signal to the
caller — the flag value came back correctly but analytics were
silently dropped.

Surface the drop through the configured error_handler so callers can
see they need to include distinct_id in the context alongside the
bucketing key.

Behavior when distinct_id IS present is unchanged.

Linear: SDK-84

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@tylerjroach tylerjroach requested review from a team and tdumitrescu June 29, 2026 17:19
@linear-code

linear-code Bot commented Jun 29, 2026

Copy link
Copy Markdown

SDK-84

@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.80%. Comparing base (e5edf71) to head (c1d30aa).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #154      +/-   ##
==========================================
+ Coverage   96.64%   96.80%   +0.15%     
==========================================
  Files          14       14              
  Lines         656      657       +1     
==========================================
+ Hits          634      636       +2     
+ Misses         22       21       -1     
Flag Coverage Δ
openfeature 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tylerjroach

Copy link
Copy Markdown
Contributor Author

@greptileai

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

Safe to merge — the change is a one-liner that adds an error handler notification before an existing early return, with no effect on the happy path.

The fix is narrowly scoped: it only activates when distinct_id is absent, a path that previously returned silently. The safe-navigation operator ensures nil error handlers are handled gracefully, existing behavior when distinct_id is present is untouched, and the new spec directly covers the added code path.

No files require special attention.

Important Files Changed

Filename Overview
lib/mixpanel-ruby/flags/flags_provider.rb Adds error handler notification before silent return when distinct_id is missing; safe navigation on @error_handler handles nil handler correctly.
spec/mixpanel-ruby/flags/local_flags_spec.rb New test verifies error handler receives a MixpanelError with flag key and distinct_id in the message; mock setup is consistent with the rest of the suite.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant LocalFlags
    participant ErrorHandler

    Caller->>LocalFlags: "get_flag(flag_key, context={device_id: ...})"
    LocalFlags-->>Caller: variant (local eval succeeds)
    LocalFlags->>LocalFlags: track_exposure_event(flag_key, variant, context)
    LocalFlags->>LocalFlags: "distinct_id = context['distinct_id'] → nil"
    LocalFlags->>ErrorHandler: handle(MixpanelError("...flag_key...distinct_id..."))
    LocalFlags-->>LocalFlags: return (no event fired)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller
    participant LocalFlags
    participant ErrorHandler

    Caller->>LocalFlags: "get_flag(flag_key, context={device_id: ...})"
    LocalFlags-->>Caller: variant (local eval succeeds)
    LocalFlags->>LocalFlags: track_exposure_event(flag_key, variant, context)
    LocalFlags->>LocalFlags: "distinct_id = context['distinct_id'] → nil"
    LocalFlags->>ErrorHandler: handle(MixpanelError("...flag_key...distinct_id..."))
    LocalFlags-->>LocalFlags: return (no event fired)
Loading

Reviews (1): Last reviewed commit: "fix(analytics): surface dropped exposure..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant