Repository navigation
Conversation
Request /flags?v=3 from the sync and async clients and keep each flag's typed value, structured reason and metadata on the parsed record. - A record with a value member is a v3 record. A record without one comes from an older server: its value is variant, or enabled without one, and its config version is 1. - A known field with the wrong JSON type fails only that record with PARSE_ERROR; unknown fields are ignored. - Legacy getters, callbacks and $feature_flag_called keep reading the legacy rendering, now derived from value in one place: a string is the variant, null is disabled, and a number or object is enabled with the value as a JSON-encoded payload. - evaluate_flags() snapshots gain get_boolean/string/number/object_value and _details accessors. They never coerce and return the caller default for missing, failed, null and mismatched values.
Contributor
posthog-python Compliance ReportDate: 2026-10-07T08:43:18.280077+00:00
|
| Test | Status | Duration |
|---|---|---|
| Endpoint And Method.Targets V1 Endpoint | ✅ | 518ms |
| Endpoint And Method.Does Not Use Legacy Endpoints | ✅ | 512ms |
| Required Headers.Has Authorization Bearer Header | ✅ | 511ms |
| Required Headers.Has Content Type Json | ✅ | 512ms |
| Required Headers.Has Posthog Sdk Info Format | ✅ | 511ms |
| Required Headers.Has Posthog Attempt Header | ✅ | 511ms |
| Required Headers.Has Posthog Request Id | ✅ | 511ms |
| Required Headers.Has Posthog Request Timestamp | ✅ | 512ms |
| Required Headers.Has User Agent | ✅ | 511ms |
| Body Format.Body Has Created At And Batch | ✅ | 512ms |
| Body Format.No Api Key In Body | ✅ | 511ms |
| Body Format.No Sent At In Body | ✅ | 511ms |
| Event Format.Event Has Required Root Fields | ✅ | 511ms |
| Event Format.Event Uuid Is Valid | ✅ | 511ms |
| Event Format.Event Timestamp Is Rfc3339 | ✅ | 511ms |
| Event Format.Non Utc Event Timestamp Is Converted To Utc | ✅ | 517ms |
| Event Format.Distinct Id Is String | ✅ | 511ms |
| Event Format.Distinct Id At Root Not Properties | ✅ | 511ms |
| Event Format.Custom Properties Preserved | ✅ | 510ms |
| Event Format.Set Properties Preserved | ✅ | 511ms |
| Event Format.Set Once Properties Preserved | ✅ | 511ms |
| Event Format.Groups Properties Preserved | ✅ | 511ms |
| Event Format.Sdk Generates Uuid If Not Provided | ✅ | 511ms |
| Event Format.Event Has Required Root Fields Batch | ✅ | 516ms |
| Event Format.Event Uuid Is Valid Batch | ✅ | 514ms |
| Event Format.Event Timestamp Is Rfc3339 Batch | ✅ | 515ms |
| Event Format.Distinct Id Is String Batch | ✅ | 514ms |
| Event Format.Distinct Id At Root Not Properties Batch | ✅ | 515ms |
| Event Format.Custom Properties Preserved Batch | ✅ | 514ms |
| Event Format.Set Properties Preserved Batch | ✅ | 515ms |
| Event Format.Set Once Properties Preserved Batch | ✅ | 514ms |
| Event Format.Groups Properties Preserved Batch | ✅ | 515ms |
| Event Format.Sdk Generates Uuid If Not Provided Batch | ✅ | 515ms |
| Batch Behavior.Multiple Events In Single Batch | ✅ | 519ms |
| Batch Behavior.Batch Envelope Smoke | ✅ | 516ms |
| Batch Behavior.Flush With No Events Sends Nothing | ✅ | 507ms |
| Batch Behavior.Flush At Triggers Batch | ✅ | 1012ms |
| Batch Behavior.Created At Reflects Batch Creation Time | ✅ | 511ms |
| Deduplication.Generates Unique Uuids | ✅ | 518ms |
| Deduplication.Different Events Same Content Different Uuids | ✅ | 513ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 6520ms |
| Deduplication.Preserves Timestamp On Retry | ✅ | 6520ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 6521ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 519ms |
| Header Behavior On Retry.Attempt Header Starts At One | ✅ | 511ms |
| Header Behavior On Retry.Attempt Header Increments On Retry | ✅ | 13531ms |
| Header Behavior On Retry.Request Id Preserved On Retry | ✅ | 6521ms |
| Header Behavior On Retry.Different Requests Have Different Request Ids | ✅ | 3022ms |
| Header Behavior On Retry.Request Timestamp Changes On Retry | ✅ | 6524ms |
| Response Format Validation.Success Response Has Uuid Keyed Results | ✅ | 512ms |
| Response Format Validation.Success Response Has Ok For Each Event | ✅ | 517ms |
| Response Format Validation.Success No Retry After When All Ok | ✅ | 513ms |
| Response Format Validation.Success Retry After Present When Retry Events | ✅ | 1519ms |
| Response Format Validation.Success No Retry After When Drop Only | ✅ | 514ms |
| Response Format Validation.Response Echoes Request Id | ✅ | 512ms |
| Retry Behavior.Retries On 408 | ✅ | 6520ms |
| Retry Behavior.Retries On 500 | ✅ | 6523ms |
| Retry Behavior.Retries On 503 | ✅ | 8521ms |
| Retry Behavior.Retries On 504 | ✅ | 6518ms |
| Retry Behavior.Retryable Errors Have Retry After | ✅ | 3517ms |
| Retry Behavior.Respects Retry After On Retryable Error | ✅ | 11519ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2514ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2516ms |
| Retry Behavior.Does Not Retry On 402 | ✅ | 2512ms |
| Retry Behavior.Does Not Retry On 413 | ✅ | 2516ms |
| Retry Behavior.Does Not Retry On 415 | ✅ | 2514ms |
| Retry Behavior.Non Retryable Errors Have No Retry After | ✅ | 2513ms |
| Retry Behavior.Implements Backoff | ✅ | 22542ms |
| Retry Behavior.Max Retries Respected | ✅ | 22535ms |
| Partial Batch Handling.Handles 200 Full Success | ✅ | 2515ms |
| Partial Batch Handling.Handles 200 With All Ok | ✅ | 3518ms |
| Partial Batch Handling.Does Not Retry Dropped Events | ✅ | 3519ms |
| Partial Batch Handling.Does Not Retry Limited Events | ✅ | 3517ms |
| Partial Batch Handling.Prunes Ok Events On Partial Retry | ✅ | 6524ms |
| Partial Batch Handling.Prunes Dropped Events On Partial Retry | ✅ | 6524ms |
| Partial Batch Handling.Retries Only Retry Events From Partial | ✅ | 6527ms |
| Partial Batch Handling.Partial Retry Preserves Uuids | ✅ | 6522ms |
| Partial Batch Handling.Partial Retry Attempt Header Increments | ✅ | 6523ms |
| Partial Batch Handling.Partial Retry Request Id Preserved | ✅ | 6523ms |
| Partial Batch Handling.Respects Retry After On Partial | ✅ | 8525ms |
| Partial Batch Handling.Unknown Result Treated As Terminal | ✅ | 3515ms |
| Partial Batch Handling.Mixed Ok Drop Limited No Retry | ✅ | 3519ms |
| Compression.Sends Gzip Content Encoding | ✅ | 512ms |
| Compression.No Content Encoding When Disabled | ✅ | 511ms |
| Compression.Compressed Body Is Decompressible | ✅ | 511ms |
| Error Handling.Does Not Retry On Unknown 4Xx | ✅ | 2514ms |
| Event Options.Cookieless Mode Override | ✅ | 511ms |
| Event Options.Disable Skew Correction Override | ✅ | 511ms |
| Event Options.Process Person Profile Override | ✅ | 511ms |
| Event Options.Product Tour Id Override | ✅ | 511ms |
| Event Options.Unset Options Omitted | ✅ | 511ms |
| Event Options.Options Override In Batch | ✅ | 514ms |
| Geoip And Historical Migration.Geoip Disable Injected Into Properties | ✅ | 512ms |
| Geoip And Historical Migration.Historical Migration Set In Body | ✅ | 511ms |
| Geoip And Historical Migration.Historical Migration Absent By Default | ✅ | 511ms |
Capture_Ai Tests
✅ 5/5 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Routing.Capture Ai Posts To Ai Endpoint | ✅ | 513ms |
| Routing.Capture Does Not Reroute Ai Named Events | ✅ | 512ms |
| Identity.Capture Ai Event Has Uuid | ✅ | 512ms |
| Identity.Capture Ai Keeps Supplied Uuid | ✅ | 512ms |
| Timestamp Format.Non Utc Event Timestamp Is Converted To Utc | ✅ | 511ms |
Feature_Flags Tests
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 12ms |
| Request Payload.Flags Request Uses V2 Query Param | ❌ | 9ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 10ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 10ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 9ms |
| Request Payload.Groups Round Trip | ✅ | 9ms |
| Request Payload.Groups Default To Empty Object | ✅ | 10ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 9ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 9ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 9ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 3ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 510ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 14ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 10ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 314ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 313ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 512ms |
Failures
request_payload.flags_request_uses_v2_query_param
Expected /flags query param v='2', got '3'. All query params: {'v': '3'}
Feature_Flags_Local_Evaluation Tests
✅ 4/4 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Versioned Boolean Matching.Matching Version Missing | ✅ | 72ms |
| Versioned Boolean Matching.Matching Version 1 | ✅ | 69ms |
| Versioned Boolean Matching.Matching Version 2 | ✅ | 68ms |
| Versioned Boolean Matching.Version Only Reload 1 2 1 2 Missing | ✅ | 36ms |
This branch has not been deployed
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.
💡 Motivation and Context
Public API change. This adds typed accessors to
FeatureFlagEvaluations(get_boolean_value/get_boolean_details, and the same for string, number and object), theFlagEvaluationDetailsandFlagEvaluationErrorCodetypes, new fields onFeatureFlag(value,failed,error_code,error_message) and new fields onFlagMetadata(config_version,rule_type,rule_id,experiment_id,variant_key,holdout_id,forced_variant). Existing getters keep their results.The flags service can now answer
/flags?v=3(PostHog/posthog#107732). A v3 record carries one typedvalue(boolean, string, number, object or null) instead ofenabledandvariant, plus a structuredreasonand rule metadata. This PR moves the SDK to that response and keeps the full record for each flag, instead of reducing it to a value.The reader follows the wire contract published in posthog-sdk-test-harness 1.13.1 (
contracts/feature_flag_rules_v2):/flags/?v=3. A record withvalueis a v3 record. A record without it comes from a server that does not serve v3 (an older or self-hosted deployment, or one with the v3 response turned off). Its value isvariant, orenabledwhen there is no variant, its config version is 1, and any rule context in it is ignored. No probing or retry is needed.PARSE_ERROR; other flags in the response are unaffected. Before, a record that was not an object made the whole response fail to parse.nullvalue, orfalseread as another type, returns the caller default without an error. Any other value of the wrong type returns the default withTYPE_MISMATCH.valuein one place, when the record is parsed: a boolean as is, a string as the variant,nullasfalse, and a number or object astruewith the value as a JSON-encoded payload. That is the shape servers send atv=2today, soget_feature_flag,get_feature_flag_result,get_feature_flag_payload,get_all_flags(_and_payloads), the snapshot getters,$feature/<key>,$active_feature_flagsand$feature_flag_calledreturn the same values for both responses.FLAG_NOT_FOUND. A failed record isGENERAL(INVALID_CONTEXTwhen a required group is missing), with the reason description as the message. Both return the caller default.$feature_flag_calledis unchanged: same properties, same deduplication. A typed accessor counts as an access, exactly likeget_flag(). Local evaluation is unchanged.Notes for reviewers
evaluate_flags()snapshot only, so they work for the sync and async clients. I did not add single-flag client methods, because the single-flag getters are deprecated in favour ofevaluate_flags(). They are easy to add if we want them.metadata.payloadnull for number and object values. To keep the legacy payload getters working,FlagMetadata.payloadholds the value encoded as compact JSON in wire key order. Decoded payloads are identical tov=2. The encoded string matches the server's except for the spelling of float exponents (1e-07here,1e-7from the server).FlagMetadata.from_json()(the older record) now setsconfig_version=1, so one existing test that compared parsed metadata by equality was updated.TYPE_MISMATCH, because that record carries those values only in the payload. Legacy getters are not affected.reason.descriptiontexts at v3 than at v2 ("Matched rule 2" instead of "Matched condition set 2").FeatureFlagResult.reasonand$feature_flag_reasonshow the v3 text once a server sends v3. Values do not change, and v1 flags are identical in every field.flags_request_uses_v2_query_paramtest asserts that the flags request usesv=2. Both compliance jobs fail on that one test, and pass everything else, until the harness acceptsv=3. This PR does not change the harness.$experiment_exposure.minor.💚 How did you test it?
posthog/test/test_flags_v3_response.py(190 tests), built on fixtures copied unchanged from the released harness contract.posthog/test/fixtures/feature_flag_rules_v2/SOURCE.jsonrecords the release, revision and file digests, and a test checks them. It covers:nullandfalsehandling,TYPE_MISMATCH);capture(flags=...)andcapture(send_feature_flags=True)against both shapes and compares every result and$feature_flag_calledevent;ruff format --check,ruff check, mypy with the baseline filter,python -W error -c "import posthog", the public API snapshot check, and the OpenFeature provider's tests.master, with the v3 response turned on and then off, reading v1 flags and new-format boolean, string, number and object flags. With it on, the typed accessors returned the typed values with their reason and rule metadata. With it off, the SDK read the older record. Legacy values, payloads and$feature_flag_calledevents were identical between the two runs, apart from the reason description text noted above.📝 Checklist
If releasing new changes
sampo addto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Written with Claude Code (Claude Opus) from a brief by the assignee. The agent surveyed the existing v2 parsing path and extended it rather than adding a parallel one: the parsed
FeatureFlagnow carries the typed value and error, and the legacy fields are derived from it once. Choices the reviewer may want to revisit are listed under "Notes for reviewers".🤖 Generated with Claude Code