Skip to content

Conversation

@dmarticus
Copy link
Contributor

@dmarticus dmarticus commented Nov 20, 2025

Problem

When debugging feature flag issues or investigating unexpected flag behavior, it's important to know exactly when a flag was evaluated. Currently, feature flag responses include a request_id for tracing, but lack timestamp information about when the evaluation occurred.

This makes it difficult to:

  • Debug timing-related issues with feature flag evaluations
  • Understand if a flag response is stale
  • Correlate flag evaluations with other time-based events

I gotta update all of the SDKs to use this field and add to as a property to the $feature_flag_called event, but those changes need this change first.

Changes

  1. Added evaluated_at field to all flag response types:

    • FlagsResponse (v2)
    • LegacyFlagsResponse (v1)
    • DecideV1Response
    • DecideV2Response
    • Field contains timestamp in milliseconds since Unix epoch
  2. Updated taxonomy definitions to include $feature_flag_evaluated_at property in:

    • core-filter-definitions-by-group.json (frontend)
    • taxonomy.py (backend)
    • ai.py (HogQL AI)
  3. Set timestamps at evaluation time using Utc::now().timestamp_millis() across all response creation paths

  4. Added comprehensive tests to verify the field is present, serialized correctly, and contains valid timestamps

How did you test this code?

  1. Added new integration test it_includes_evaluated_at_timestamp_in_response() that:

    • Verifies the field exists in v2 response format
    • Validates timestamp is within expected range (between before/after request)
    • Confirms field is serialized as evaluatedAt in JSON
    • Tests v1 legacy response format includes the field
  2. Added unit test test_evaluated_at_field_is_present() to verify serialization

Changelog: (features only) Is this feature complete?

Yes

@dmarticus dmarticus changed the title feat(flags): add evaluatedAt to /flags response and support displaying $feature_flag_evaluated_at` in the event property taxonomy feat(flags): add evaluatedAt to /flags response and support displaying $feature_flag_evaluated_at in the event property taxonomy Nov 20, 2025
@wiz-7ad640923b
Copy link

wiz-7ad640923b bot commented Nov 20, 2025

Wiz Scan Summary

Scanner Findings
Vulnerability Finding Vulnerabilities -
Data Finding Sensitive Data 1 Low 1 Info
Secret Finding Secrets -
IaC Misconfiguration IaC Misconfigurations -
SAST Finding SAST Findings -
Total 1 Low 1 Info

View scan details in Wiz

To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 20, 2025

Size Change: +190 B (+0.01%)

Total Size: 3.43 MB

ℹ️ View Unchanged
Filename Size Change
frontend/dist/toolbar.js 3.43 MB +190 B (+0.01%)

compressed-size-action

@dmarticus dmarticus marked this pull request as ready for review November 20, 2025 16:16
@dmarticus dmarticus requested a review from a team as a code owner November 20, 2025 16:16
@posthog-bot posthog-bot requested review from a team November 20, 2025 16:17
@posthog-project-board-bot posthog-project-board-bot bot moved this to In Review in Feature Flags Nov 20, 2025
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

11 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

flags: flags_response.flags,
quota_limited: None,
request_id,
evaluated_at: Utc::now().timestamp_millis(),
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: timestamp captured after evaluation completes, not when it starts - inconsistent with field's purpose

The evaluated_at timestamp is captured AFTER evaluate_flags_with_overrides() completes and after the timing guard finishes. For debugging purposes, the timestamp should represent when the evaluation began, not ended. Consider capturing the timestamp before line 235 (let eval_timer = ...) and passing it through the evaluation methods.

Suggested change
evaluated_at: Utc::now().timestamp_millis(),
evaluated_at: eval_start_time,
Prompt To Fix With AI
This is a comment left during a code review.
Path: rust/feature-flags/src/flags/flag_matching.rs
Line: 277:277

Comment:
**logic:** timestamp captured after evaluation completes, not when it starts - inconsistent with field's purpose

The `evaluated_at` timestamp is captured AFTER `evaluate_flags_with_overrides()` completes and after the timing guard finishes. For debugging purposes, the timestamp should represent when the evaluation began, not ended. Consider capturing the timestamp before line 235 (`let eval_timer = ...`) and passing it through the evaluation methods.

```suggestion
            evaluated_at: eval_start_time,
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

Choose a reason for hiding this comment

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

For debugging purposes, the timestamp should represent when the evaluation began, not ended.

That seems arbitrary. "evaluated" implies past tense. So it should reflect when it was done being evaluated.

flags: evaluated_flags_map,
quota_limited: None,
request_id,
evaluated_at: Utc::now().timestamp_millis(),
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: inconsistent timestamp capture points across error paths

The timestamp is captured at different points depending on which code path is taken (lines 277, 482, 498, 529). This creates inconsistent semantics - some timestamps represent when evaluation completed, others when errors occurred. All paths should capture the same logical point in time for consistency.

Prompt To Fix With AI
This is a comment left during a code review.
Path: rust/feature-flags/src/flags/flag_matching.rs
Line: 482:482

Comment:
**logic:** inconsistent timestamp capture points across error paths

The timestamp is captured at different points depending on which code path is taken (lines 277, 482, 498, 529). This creates inconsistent semantics - some timestamps represent when evaluation completed, others when errors occurred. All paths should capture the same logical point in time for consistency.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

Choose a reason for hiding this comment

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

I disagree. I think the evaluated_at should be captured when the FlagsResponse is created, which is what this PR does.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds timestamp tracking to feature flag evaluation responses by introducing an evaluated_at field (in milliseconds since Unix epoch) across all flag response types, and updates taxonomy definitions to support the new $feature_flag_evaluated_at event property.

Key changes:

  • Added evaluated_at: i64 field to FlagsResponse, LegacyFlagsResponse, DecideV1Response, and DecideV2Response
  • Updated taxonomy definitions in Python backend, HogQL AI, and frontend JSON to document the new property
  • Added comprehensive tests verifying timestamp presence, serialization, and value validity

Reviewed Changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
rust/feature-flags/src/api/types.rs Added evaluated_at field to all response structs with documentation, updated constructor and conversion methods
rust/feature-flags/src/flags/flag_matching.rs Set timestamp during flag evaluation at response construction points
rust/feature-flags/src/handler/mod.rs Set timestamp when creating early-exit responses for disabled flags
rust/feature-flags/src/handler/flags.rs Set timestamp for disabled/empty flags scenarios
rust/feature-flags/src/handler/billing.rs Set timestamp for quota-limited responses
rust/feature-flags/src/handler/config_response_builder.rs Added timestamp to test helper response construction
rust/feature-flags/src/api/endpoint.rs Set timestamp for minimal flags response
rust/feature-flags/tests/test_flags.rs Added integration test verifying timestamp in v1/v2 responses and basic validation in existing test
posthog/taxonomy/taxonomy.py Added $feature_flag_evaluated_at property definition for backend taxonomy
posthog/hogql/ai.py Added $feature_flag_evaluated_at property definition for HogQL AI
frontend/src/taxonomy/core-filter-definitions-by-group.json Added $feature_flag_evaluated_at property definition for frontend

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

flags: flags_response.flags,
quota_limited: None,
request_id,
evaluated_at: Utc::now().timestamp_millis(),
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

[nitpick] The evaluated_at timestamp is generated at the end of flag evaluation rather than at the beginning. This means the timestamp represents when the response was constructed, not when the evaluation started. For debugging timing issues, it would be more accurate to capture the timestamp at the start of the evaluation process (before any flag processing occurs).

Consider capturing the timestamp once at the beginning of evaluate_all_feature_flags and passing it through to the FlagsResponse construction, rather than generating it multiple times throughout the evaluation pipeline.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@haacked haacked left a comment

Choose a reason for hiding this comment

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

Nice.

The only minor nit would be that we call Utc::now().timestamp_millis() every where we create a FlagResponse.

It seems to me the new method should do this automatically so we don't have to do this everywhere and makes it clear we intend this timestamp to be created when we've created the FlagResponse.

We could add a test helper with_evaluated_at() method for creating a FlagResponse with a specific timestamp for unit test if needed.

But this is not a blocker, just a minor nit.

flags: flags_response.flags,
quota_limited: None,
request_id,
evaluated_at: Utc::now().timestamp_millis(),
Copy link
Contributor

Choose a reason for hiding this comment

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

For debugging purposes, the timestamp should represent when the evaluation began, not ended.

That seems arbitrary. "evaluated" implies past tense. So it should reflect when it was done being evaluated.

flags: evaluated_flags_map,
quota_limited: None,
request_id,
evaluated_at: Utc::now().timestamp_millis(),
Copy link
Contributor

Choose a reason for hiding this comment

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

I disagree. I think the evaluated_at should be captured when the FlagsResponse is created, which is what this PR does.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-project-automation github-project-automation bot moved this from In Review to Approved in Feature Flags Nov 20, 2025
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.

4 participants