Conversation
Implements the Hooks specification (evaluation series + track series) to give users and LaunchDarkly integration packages an extension point for observing flag evaluations and track calls. - Public API under `LaunchDarkly\Hooks`: `Hook` (abstract base class), `Metadata`, `EvaluationSeriesContext`, `TrackSeriesContext`. - `LDClient` accepts a `hooks` option at construction and exposes `addHook()` for runtime registration. - `beforeEvaluation` / `afterEvaluation` fire around every `variation`, `variationDetail`, and `migrationVariation` call, with `afterEvaluation` running in reverse registration order per spec 1.3.4. - `afterTrack` fires after a custom event is enqueued. It does not fire for `trackMigrationOperation` (not a custom event) or for `track` calls rejected due to an invalid context. - Hook exceptions are caught and logged at the error level; when a stage errors, subsequent stages receive the previous successful stage's data (spec 1.3.7.1). - `environmentId` is intentionally omitted from the series contexts until the SDK can populate it reliably; the spec lists it as optional. - Identify series (1.4) and configuration handlers (1.5) are skipped because they are client-side only. - Contract test service declares `evaluation-hooks` and `track-hooks` capabilities and wires harness-provided hook configs through a new `PostingHook` implementation.
CI flagged an UnusedBaselineEntry for NullEventProcessor's UnusedClass error. The entry was added when I regenerated the baseline locally, but CI's psalm doesn't produce that error — so the baseline entry is stale from CI's perspective. Remove it to match what CI sees. The root cause of the local/CI divergence appears to be environmental (same psalm 6.16.1, --no-cache, same composer.lock) but isn't worth chasing for this PR.
kinyoklion
reviewed
Apr 21, 2026
Co-authored-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com>
kinyoklion
approved these changes
Apr 21, 2026
RecordingHook took its shared log as a by-reference constructor-promoted parameter (`private ?array &$sharedLog`). PHP CS Fixer 3.80.0 — the version CI ends up running under the prefer-lowest matrix entry — has a bug in its visibility_required fixer that mutates this into invalid syntax (`private ?array &public $sharedLog`), breaking `make lint`. Swap the reference for a small CallLog value object that holds a mutable calls array. Cross-hook ordering tests read `$shared->calls` instead of relying on PHP array-by-reference semantics.
The previous assertion indexed `$a->calls[0]` (the beforeEvaluation record), whose `data` field is always `[]` because HookRunner always passes empty data into every hook's beforeEvaluation. That made the test trivially pass regardless of whether the spec 1.3.7.1 fallback logic actually worked. Switch the assertion to `$a->calls[1]` (the afterEvaluation record), where `data` is the value HookRunner chose to pass forward after beforeEvaluation errored — which is what the test is meant to verify. Also add explicit stage-name assertions so the intent is clear.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 250a6a2. Configure here.
variationDetailInternal previously called evaluateInternal unguarded, so a \Throwable that is not a \Exception (e.g. a \TypeError from a downstream component) would bypass evaluateInternal's catch(\Exception) and skip afterEvaluation, violating spec 1.3.4. Wrap the evaluation + afterEvaluation call in try/finally with a fallback EvaluationDetail initialized to EXCEPTION_ERROR. On normal completion, $detail is overwritten with the real detail before the finally runs; on propagation, the finally sees the fallback and afterEvaluation still fires before the Throwable continues up the stack.
PHP's \Error hierarchy (TypeError, ArgumentCountError, etc.) extends \Throwable but not \Exception, so a downstream component raising a TypeError during evaluation bypassed the safety net and propagated up — also skipping afterEvaluation, violating spec 1.3.4. Broadening the catch to \Throwable means the SDK always returns a valid detail (with an EXCEPTION_ERROR reason when the underlying evaluation fails unexpectedly), which keeps variation calls non-throwing and lets variationDetailInternal keep its straight-line hook wiring. Replaces the try/finally wrapping in variationDetailInternal from the previous commit with the simpler root-cause fix.
kinyoklion
approved these changes
Apr 22, 2026
keelerm84
pushed a commit
that referenced
this pull request
Apr 28, 2026
🤖 I have created a release *beep* *boop* --- ## [6.8.0](6.7.0...6.8.0) (2026-04-28) ### Features * add hooks support ([#240](#240)) ([77a644d](77a644d)) * expose environmentId on EvaluationSeriesContext ([#242](#242)) ([5fcbce4](5fcbce4)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

Implements the Hooks specification (evaluation series + track series) to
give users and LaunchDarkly integration packages an extension point for
observing flag evaluations and track calls.
LaunchDarkly\Hooks:Hook(abstract base class),Metadata,EvaluationSeriesContext,TrackSeriesContext.LDClientaccepts ahooksoption at construction and exposesaddHook()for runtime registration.beforeEvaluation/afterEvaluationfire around everyvariation,variationDetail, andmigrationVariationcall, withafterEvaluationrunning in reverse registration order per spec 1.3.4.
afterTrackfires after a custom event is enqueued. It does not firefor
trackMigrationOperation(not a custom event) or fortrackcallsrejected due to an invalid context.
errors, subsequent stages receive the previous successful stage's data
(spec 1.3.7.1).
environmentIdis intentionally omitted from the series contexts untilthe SDK can populate it reliably; the spec lists it as optional.
because they are client-side only.
evaluation-hooksandtrack-hookscapabilities and wires harness-provided hook configs through a new
PostingHookimplementation.Note
Medium Risk
Adds synchronous hook execution around flag evaluation and custom event tracking, which can affect evaluation latency/ordering and introduces new error-isolation/logging paths in core client flows. Risk is mitigated by no-op defaults,
hasHooks()fast-path, and tests covering ordering and exception handling.Overview
Adds a new public hooks API (
LaunchDarkly\Hooks\Hook,Metadata,EvaluationSeriesContext,TrackSeriesContext) and wires hook execution intoLDClient.LDClientnow accepts ahooksconstructor option and a runtimeaddHook()method; it runsbeforeEvaluation/afterEvaluationaroundvariation,variationDetail, andmigrationVariation(with reverse order forafterEvaluation), and runsafterTrackafter successfultrack()calls. Hook stage failures are caught/logged and isolated via the new internalImpl\Hooks\HookRunner, and evaluation error handling is widened to catch\Throwable.Updates the contract test service to advertise
evaluation-hooks/track-hooksand to instantiate harness-configured hooks via a newPostingHook, and adds unit tests for ordering, data propagation, and exception isolation. Also updates the Psalm baseline for the new code and version.Reviewed by Cursor Bugbot for commit c53211b. Bugbot is set up for automated code reviews on this repo. Configure here.