Skip to content

feat: Expose exceptions on evaluation reasons - #290

Merged
keelerm84 merged 4 commits into
launchdarkly:mainfrom
hidingmyname:retain-evaluation-exceptions-284
Nov 9, 2026
Merged

keelerm84 merged 4 commits into
launchdarkly:mainfrom
hidingmyname:retain-evaluation-exceptions-284

Conversation

@hidingmyname

@hidingmyname hidingmyname commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

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

Related issues

Fixes #284.

Describe the solution you've provided

  • Add EvaluationReason::exception(\Throwable) and getException(): ?\Throwable, retaining the original object for EXCEPTION reasons. Existing reason factories remain unchanged.
  • Preserve the exception through withBigSegmentsEvaluationStatus() without adding it to the reason's JSON representation or analytics events.
  • Use the factory in the evaluator's unexpected-error catch, the client's unexpected-error catch, and the UnrecoverableHTTPStatusException catch for HTTP 401/403. Existing fallback values and the client's offline transition are preserved.
  • Put reason, evaluator, and client regressions in the existing test files, as requested. Cover PHP Error, both HTTP status codes, serialization privacy, and the post-authentication-error offline state.
  • Document the new accessor and add a release note. Register the external accessor beside the existing reason getters in the Psalm baseline, since src-only analysis cannot see SDK consumers.

Describe alternatives you've considered

Leaving the cause only in the PSR-3 logs does not let the caller associate the original exception with its failed variationDetail() call. A separate exception() factory also keeps exceptions out of unrelated error kinds.

Validation

PHP 8.4.27 / PHPUnit 9.6.38:

  • Before propagation was fixed: the evaluator case and all four client cases failed because getException() returned null.
  • After the fix: the targeted regressions passed. Full PHPUnit run: 538 tests, 1,406 assertions, 16 integration tests skipped, no failures. The external WireMock integration suite was not run locally.
  • Standalone smoke with the real Guzzle requester against a local HTTP server: both 401 and 403 expose UnrecoverableHTTPStatusException, keep reason JSON unchanged, and switch subsequent evaluations offline; the server observed exactly one request per client.
  • PHP CS Fixer: no changes required.
  • Psalm: no new errors; it reports the existing UnusedClass for NullEventProcessor, also reproduced on unmodified upstream 6df19b5 with the same dependency set. I have not suppressed or changed that unrelated issue.

Only PHP 8.4 was exercised locally; the existing CI matrix covers the other supported PHP versions and platforms.

Additional context

AI disclosure: this contribution was implemented and submitted by the omp AI coding assistant in the omp harness on behalf of @hidingmyname, at their request. No review reminder or reviewer assignment is requested.


Note

Overview
Failed flag evaluations can now surface the original Throwable on the evaluation reason, without changing analytics JSON or serialized payloads.

EvaluationReason gains exception(\Throwable) and getException(): ?\Throwable. The cause is kept for ERROR / EXCEPTION reasons (including through withBigSegmentsEvaluationStatus()), but is omitted from jsonSerialize(), __sleep() / unserialize(), and reason strings—error(EXCEPTION_ERROR) still leaves getException() null.

Evaluator and LDClient (variationDetail paths, including UnrecoverableHTTPStatusException on 401/403) now build reasons with EvaluationReason::exception($e) instead of a bare EXCEPTION error kind, while existing default-value fallbacks and the client’s offline transition after auth errors stay the same.

Tests cover serialization privacy, prerequisite lookup failures, and several exception types on the client.

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

Retain the original Throwable in evaluator and client failures, including unrecoverable HTTP 401/403 responses, without changing reason JSON or fallback behavior.
@hidingmyname
hidingmyname requested a review from a team as a code owner November 9, 2026 13:44
@keelerm84 keelerm84 changed the title feat: expose exceptions on evaluation reasons feat: Expose exceptions on evaluation reasons Nov 9, 2026
@keelerm84
keelerm84 merged commit e36a6fd into launchdarkly:main Nov 9, 2026
12 checks passed
@keelerm84

Copy link
Copy Markdown
Member

Thank you @hidingmyname for getting this PR put up. I appreciate your contribution, and you should receive a notice once the release has gone out.

kinyoklion pushed a commit that referenced this pull request Nov 9, 2026
🤖 I have created a release *beep* *boop*
---


##
[6.12.0](6.11.0...6.12.0)
(2026-11-09)


### Features

* Expose exceptions on evaluation reasons
([#290](#290))
([e36a6fd](e36a6fd))


### Bug Fixes

* Fix loose key and value comparisons and big segment rule fall-through
([#286](#286))
([4eb7394](4eb7394))

---
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 6.12.0** bumps the package from **6.11.0** to **6.12.0** in
`.release-please-manifest.json`, `LDClient::VERSION`, and the docs build
action title, and adds the **6.12.0** section to `CHANGELOG.md`.
> 
> That release notes two behavior changes already on `main`:
**evaluation reasons can surface the underlying `Throwable`** via
`EvaluationReason::getException()` (documented on `variationDetail()`),
and **stricter key/value matching plus a big-segment rule fall-through
fix** for flag evaluation.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
0a42312. 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.

Expose the exception behind an EXCEPTION evaluation reason (EvaluationReason::getException())

2 participants