Skip to content

fix: return the default value for an invalid context without creating analytics events - #204

Merged
kinyoklion merged 1 commit into
mainfrom
rlamb/fix-invalid-context-evaluation
Oct 8, 2026
Merged

kinyoklion merged 1 commit into
mainfrom
rlamb/fix-invalid-context-evaluation

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions (run locally on OTP 25.3; CI covers the rest)

Related issues

Found while reviewing #202. The fix is independent of that PR and applies to its branch unchanged (the evaluator is not modified there).

Describe the solution you've provided

Evaluating a flag with an invalid context (a missing key, or a kind or key that is not a binary) only validated the context once the flag had been found. When the flag was unknown or deleted, or when evaluating it raised, the evaluator still created a feature event carrying the invalid context, and the event server crashed when it derived the context's canonical key for index-event deduplication.

Each such evaluation lost the buffered events and the open summary. A burst of them exceeded the event supervisor's restart intensity and restarted the instance supervisor, which comes back without its feature store and update processor while initialized/1 still returns true. With the synchronous add_event on main the caller at least saw the exit; with the asynchronous pipeline in #202 the failure is silent.

Every place that creates an event now validates the context first. The unknown-flag, deleted-flag and exception results go through one helper (unknown_flag_result/4) that returns the default value with {error, user_not_specified}, logs a warning and records no event when the context is invalid, as the other server-side SDKs do. The known-flag path keeps its existing check in flag_for_context_check_valid/5 and now logs the same warning. A valid context is validated exactly once per evaluation, so the known-flag path costs the same as before; the unknown-flag path pays one is_valid/2 call (about 130 ns for a single-kind context) on top of the warning it already logs. ldclient_context:is_valid/2 is wrapped so a term that is not a context at all counts as invalid instead of raising.

Tests: unknown_flag_with_invalid_context (five invalid shapes against an unknown, a deleted and a known flag), exception_with_invalid_context_sends_no_event (the feature store raising, as it does once the store has been lost; a valid context keeps the existing exception event, an invalid one records nothing) and invalid_context_does_not_crash_the_event_server (public API; the event server keeps its pid and still delivers afterwards). All three fail on main without the change.

Describe alternatives you've considered

Checking the context once at the top of flag_key_for_context/4 is simpler to reason about but validates known flags twice (the per-flag check is still needed by all_flags_state, which calls the lower-level function directly) and changes the result for an invalid context on an offline or not-yet-initialised instance from client_not_ready to user_not_specified. Guarding ldclient:variation/4 and variation_detail/4 the way identify and track already do would cover the public API but not internal callers. Making the event server tolerate a bad context would hide the error from the caller and would have to be done separately on main and in #202.

Additional context

Also reproducible with ldclient:variation(<<"missing-flag">>, #{kind => <<"org">>, key => 42}, false).


Note

Overview
Invalid contexts no longer emit analytics events on unknown-flag, deleted-flag, or evaluation-exception paths—preventing the event server from crashing when it canonicalizes a bad context.

Evaluation now routes those outcomes through unknown_flag_result/4, which checks is_valid_context/1 first. Valid contexts keep prior behavior (including unknown-flag events and warnings for flag_not_found). Invalid contexts use shared invalid_context_result/2: default value, {error, user_not_specified}, a warning, and no events—aligned with the known-flag path and other server SDKs.

The exception handler in flag_key_for_context/4 uses the same helper instead of always building an unknown-flag event. flag_for_invalid_context now logs the same invalid-context warning via invalid_context_result.

Tests cover invalid context shapes against unknown/deleted/known flags, exception + invalid context (no event), and that the event server stays alive through the public API.

Reviewed by Cursor Bugbot for commit 5315a2c. Bugbot is set up for automated code reviews on this repo. Configure here.

@kinyoklion
kinyoklion requested a review from a team as a code owner October 6, 2026 23:20
… analytics events

Evaluating a flag with an invalid context (a missing key, or a kind or key
that is not a binary) only validated the context once the flag had been
found. When the flag was unknown or deleted, or when evaluating it raised,
the evaluator still created a feature event carrying the invalid context,
and the event server crashed when it derived the context's canonical key for
index-event deduplication. Each such evaluation lost the buffered events and
the open summary; a burst of them exceeded the event supervisor's restart
intensity and restarted the instance supervisor, which came back without its
feature store and update processor while initialized/1 still returned true.
With the synchronous add_event the caller at least saw the exit; with the
asynchronous pipeline in #202 the failure is silent.

Every place that creates an event now validates the context first. The
unknown-flag, deleted-flag and exception results go through one helper that
returns the default value with {error, user_not_specified}, logs a warning
and records no event when the context is invalid, as the other server-side
SDKs do; the known-flag path keeps its existing check and now logs the same
warning. A valid context is therefore validated exactly once per evaluation
and the known-flag path costs the same as before. is_valid/2 is wrapped so a
term that is not a context at all counts as invalid instead of raising.
@kinyoklion
kinyoklion force-pushed the rlamb/fix-invalid-context-evaluation branch from 9c864bc to 5315a2c Compare October 8, 2026 16:56
@kinyoklion
kinyoklion merged commit 0bc26ea into main Oct 8, 2026
10 checks passed
@kinyoklion
kinyoklion deleted the rlamb/fix-invalid-context-evaluation branch October 8, 2026 21:58
kinyoklion pushed a commit that referenced this pull request Oct 21, 2026
🤖 I have created a release *beep* *boop*
---


##
[3.12.1](v3.12.0...v3.12.1)
(2026-10-20)


### Bug Fixes

* Filter emSign ECC Root CA - G3 out of the bundled certifi store
([#206](#206))
([6c5980d](6c5980d))
* return the default value for an invalid context without creating
analytics events
([#204](#204))
([0bc26ea](0bc26ea))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> **Release 3.12.1** bumps the SDK version from **3.12.0** to **3.12.1**
in the Release Please manifest, `ldclient.app.src`, and the `VERSION`
constant in `ldclient_config`, and adds a **CHANGELOG** entry for
2026-10-20.
> 
> This patch release packages two bug fixes already on `main`: filtering
**emSign ECC Root CA - G3** out of the bundled **certifi** trust store
when TLS falls back to certifi, and returning flag **defaults for
invalid contexts** without emitting analytics events.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f65f80f. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

2 participants