Skip to content

fix: reset flag-called tracking when definitions reload - #267

Merged
marandaneto merged 3 commits into
mainfrom
fix/triage-ruby-189-20260905
Sep 7, 2026
Merged

marandaneto merged 3 commits into
mainfrom
fix/triage-ruby-189-20260905

Conversation

@marandaneto

@marandaneto marandaneto commented Sep 5, 2026

Copy link
Copy Markdown
Member

💡 Motivation and Context

Related to #189.

After local flag definitions reload, the client still remembers earlier $feature_flag_called events and suppresses the next access. Clear the existing tracker when definitions are applied or when a quota response discards loaded definitions. Manual reloads, background polling, and external-cache refreshes use the same callback.

Definition publication and tracker reset now share the existing deduplication mutex, closing the race where a refresh erased an event recorded against newly published definitions. Network requests, cache-provider callbacks, and logging stay outside that lock. Repeated reads between reloads remain deduplicated. HTTP 304 responses, failed requests, and responses without definitions leave tracking unchanged. Evaluations already in flight may still complete using definitions they read before a refresh.

💚 How did you test it?

  • Added 13 regression examples that inspect queued events, covering manual and background reloads, external-cache refreshes, empty definitions, quota discard, shared snapshot tracking, failure preservation, and concurrent duplicate reads.
  • Reproduction against the unchanged base failed 8 of those 13 examples. All 13 pass with this fix. Ten seeded regression runs also passed during implementation.
  • Added six deterministic concurrency cases spanning network refreshes, external-cache refreshes, and quota clearing through both single-flag and snapshot access. All six reproduced duplicate events before the follow-up fix (expected one event, got two), and pass afterward.
  • Re-ran bundle exec rspec: 696 examples, 0 failures. The 19-example reload suite also passed ten seeded runs (190 examples).
  • Re-ran bundle exec rubocop (85 files), bundle exec rake public_api:check, and the core gem build. All passed. The gem build retains its existing missing-author warning.
  • Original branch autoreview passed for 4f4b1de269fc651c7a79a1ba0f09d4ed0c90bf96. Local autoreview of the concurrency follow-up, published as d0b0315e801c64c409e38c3555619950e8f6eb74, also passed with no actionable findings.

Validation used Ruby 4.0.6 and Bundler 4.0.13 with frozen dependencies. The CI Ruby 3.2/3.3/3.4 matrix was not run locally. Tests use WebMock rather than a live PostHog service.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed. No documentation change was needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

The changeset selects patch releases for both posthog-ruby and posthog-rails. Rails delegates feature-flag access to the core client and pins the exact core SDK version, so it needs a release to receive this fix. The changeset was edited directly; the generator was not run.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Pi agents implemented and independently reviewed this user-directed fix. Tools used were Git, Bundler/RSpec, RuboCop, Rake, RubyGems, GitHub CLI, and the isolated Pi autoreview helper. Session references: implementation 3efed453-bba9-4ebc-853b-b7247693a8d8, publication 510ebebc-a750-45a5-88ce-92821ccd3256 (local sessions, no public link).

The fix keeps the existing bounded tracker and mutex rather than adding generation tracking or changing the public client API. Human review is required before merging.

@marandaneto marandaneto self-assigned this Sep 5, 2026
@marandaneto
marandaneto marked this pull request as ready for review September 5, 2026 17:26
@marandaneto
marandaneto requested a review from a team as a code owner September 5, 2026 17:26
@marandaneto
marandaneto requested a review from a team September 5, 2026 17:26
@posthog-project-board-bot posthog-project-board-bot Bot moved this to In Review in Feature Flags Sep 5, 2026
@marandaneto

Copy link
Copy Markdown
Member Author

maybe a better approach would be to check the results and the cached flags and only clear the values that have changed, wdyt? or is it ok to emit again? i recall those events being "expensive" but later this was improved during ingestion

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

posthog-ruby Compliance Report

Date: 2026-09-07 19:49:17 UTC
Duration: 98215ms

✅ All Tests Passed!

46/46 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 8ms
Format Validation.Event Has Uuid 106ms
Format Validation.Event Has Lib Properties 107ms
Format Validation.Distinct Id Is String 106ms
Format Validation.Token Is Present 106ms
Format Validation.Custom Properties Preserved 106ms
Format Validation.Event Has Timestamp 106ms
Retry Behavior.Retries On 503 5312ms
Retry Behavior.Does Not Retry On 400 2108ms
Retry Behavior.Does Not Retry On 401 2108ms
Retry Behavior.Respects Retry After Header 8014ms
Retry Behavior.Implements Backoff 15713ms
Retry Behavior.Retries On 500 5213ms
Retry Behavior.Retries On 502 5211ms
Retry Behavior.Retries On 504 5212ms
Retry Behavior.Max Retries Respected 15621ms
Deduplication.Generates Unique Uuids 111ms
Deduplication.Preserves Uuid On Retry 5211ms
Deduplication.Preserves Uuid And Timestamp On Retry 10310ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 5215ms
Deduplication.No Duplicate Events In Batch 111ms
Deduplication.Different Events Have Different Uuids 106ms
Compression.Sends Gzip When Enabled 106ms
Batch Format.Uses Proper Batch Structure 105ms
Batch Format.Flush With No Events Sends Nothing 4ms
Batch Format.Multiple Events Batched Together 110ms
Error Handling.Does Not Retry On 403 2107ms
Error Handling.Does Not Retry On 413 2108ms
Error Handling.Retries On 408 5211ms

Feature_Flags Tests

17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 107ms
Request Payload.Flags Request Uses V2 Query Param 106ms
Request Payload.Flags Request Hits Flags Path Not Decide 106ms
Request Payload.Flags Request Omits Authorization Header 107ms
Request Payload.Token In Flags Body Matches Init 107ms
Request Payload.Groups Round Trip 107ms
Request Payload.Groups Default To Empty Object 106ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False 105ms
Request Payload.Disable Geoip Omitted Defaults To False 106ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key 106ms
Request Lifecycle.No Flags Request On Init Alone 3ms
Request Lifecycle.No Flags Request On Normal Capture 105ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests 108ms
Request Lifecycle.Mock Response Value Is Returned To Caller 107ms
Retry Behavior.Retries Flags On 502 246ms
Retry Behavior.Retries Flags On 504 207ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event 108ms

@greptile-apps

greptile-apps Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
lib/posthog/feature_flags.rb:1288
**Refresh Can Duplicate Events**

During a background refresh, the new definitions become visible before this callback clears the deduplication tracker. A concurrent evaluation can use those new definitions and record its `$feature_flag_called` event, but the callback then erases that record. A later read emits the event again even though no additional reload occurred. The tracker reset must be ordered with definition publication, such as by associating entries with a definition generation.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix: reset flag-called tracking when def..." | Re-trigger Greptile

Comment thread lib/posthog/feature_flags.rb Outdated
@dustinbyrne

dustinbyrne commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

maybe a better approach would be to check the results and the cached flags and only clear the values that have changed, wdyt? or is it ok to emit again? i recall those events being "expensive" but later this was improved during ingestion

we could also key off distinct id and value - so if the evaluation result changes it'll be emitted, otherwise it's deduplicated immediately. i think we're doing this elsewhere, i have an agent looking into it.

we're already doing this. ingestion does deduplicate these as well.

@dustinbyrne dustinbyrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing dedupe key already includes distinct ID, flag key, response and normalized groups. Resetting the tracker when definitions are applied matches its lifecycle contract; 304 and failed refreshes preserve tracking.

@github-project-automation github-project-automation Bot moved this from In Review to Approved in Feature Flags Sep 7, 2026
@posthog-project-board-bot posthog-project-board-bot Bot moved this from Approved to In Review in Feature Flags Sep 7, 2026
@marandaneto
marandaneto merged commit 6e2b0b0 into main Sep 7, 2026
21 checks passed
@marandaneto
marandaneto deleted the fix/triage-ruby-189-20260905 branch September 7, 2026 19:51
@github-project-automation github-project-automation Bot moved this from In Review to Done in Feature Flags Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants