Skip to content

fix(compliance): add /get_feature_flag endpoint to the test adapter#542

Open
turnipdabeets wants to merge 7 commits into
mainfrom
fix/compliance-adapter-get-feature-flag
Open

fix(compliance): add /get_feature_flag endpoint to the test adapter#542
turnipdabeets wants to merge 7 commits into
mainfrom
fix/compliance-adapter-get-feature-flag

Conversation

@turnipdabeets
Copy link
Copy Markdown
Contributor

@turnipdabeets turnipdabeets commented May 29, 2026

TL;DR: the compliance adapter was missing /get_feature_flag → harness 404s → 0/16 flag tests. This adds the endpoint (mirrors posthog-ios).

turnipdabeets and others added 2 commits May 29, 2026 11:43
The harness was getting a 404 on /get_feature_flag, failing all 16
feature-flag tests. Implements the endpoint (mirrors posthog-ios): set
person/group properties, reload flags, resolve the value, flush.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Satisfies the semgrep package-manager rules: add a 7-day cooldown to the
Dependabot github-actions ecosystem and minimumReleaseAge: 10080 to the
pnpm workspace, so newly published packages wait before being adopted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

posthog-android Compliance Report

Date: 2026-06-02 23:27:44 UTC
Duration: 181597ms

⚠️ Some Tests Failed

38/45 tests passed, 7 failed


Capture Tests

⚠️ 28/29 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 2345ms
Format Validation.Event Has Uuid 2023ms
Format Validation.Event Has Lib Properties 2025ms
Format Validation.Distinct Id Is String 2025ms
Format Validation.Token Is Present 2021ms
Format Validation.Custom Properties Preserved 2022ms
Format Validation.Event Has Timestamp 2022ms
Retry Behavior.Retries On 503 7025ms
Retry Behavior.Does Not Retry On 400 4022ms
Retry Behavior.Does Not Retry On 401 4025ms
Retry Behavior.Respects Retry After Header 7025ms
Retry Behavior.Implements Backoff 17033ms
Retry Behavior.Retries On 500 7024ms
Retry Behavior.Retries On 502 7025ms
Retry Behavior.Retries On 504 7027ms
Retry Behavior.Max Retries Respected 17021ms
Deduplication.Generates Unique Uuids 2038ms
Deduplication.Preserves Uuid On Retry 7023ms
Deduplication.Preserves Uuid And Timestamp On Retry 12031ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 7028ms
Deduplication.No Duplicate Events In Batch 2032ms
Deduplication.Different Events Have Different Uuids 2026ms
Compression.Sends Gzip When Enabled 2022ms
Batch Format.Uses Proper Batch Structure 2018ms
Batch Format.Flush With No Events Sends Nothing 2013ms
Batch Format.Multiple Events Batched Together 2029ms
Error Handling.Does Not Retry On 403 4020ms
Error Handling.Does Not Retry On 413 4019ms
Error Handling.Retries On 408 7025ms

Failures

error_handling.does_not_retry_on_413

Expected 1 requests, got 2

Feature_Flags Tests

⚠️ 10/16 tests passed, 6 failed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 2028ms
Request Payload.Flags Request Uses V2 Query Param 2017ms
Request Payload.Flags Request Hits Flags Path Not Decide 2015ms
Request Payload.Flags Request Omits Authorization Header 2017ms
Request Payload.Token In Flags Body Matches Init 2017ms
Request Payload.Groups Round Trip 2021ms
Request Payload.Groups Default To Empty Object 2017ms
Request Payload.Person Properties Distinct Id Auto Populated When Caller Omits It 2029ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False 2015ms
Request Payload.Disable Geoip Omitted Defaults To False 2018ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key 2017ms
Request Lifecycle.No Flags Request On Init Alone 8ms
Request Lifecycle.No Flags Request On Normal Capture 2016ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests 4024ms
Request Lifecycle.Mock Response Value Is Returned To Caller 2016ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event 4022ms

Failures

request_payload.request_with_person_properties_device_id

Expected distinct_id='test_user_123', got '019e8aa9-9556-71ed-83bf-6d6f7b24b4b2'

request_payload.groups_default_to_empty_object

Field 'groups' not found in /flags request body at path 'groups'. Available keys: ['$anon_distinct_id', '$device_id', 'api_key', 'distinct_id', 'timezone', 'person_properties']

request_payload.person_properties_distinct_id_auto_populated_when_caller_omits_it

Field 'distinct_id' not found in /flags request body at path 'person_properties.distinct_id'. Available keys: ['$lib', '$lib_version', 'email']

request_payload.disable_geoip_false_propagates_as_geoip_disable_false

Field 'geoip_disable' not found in /flags request body at path 'geoip_disable'. Available keys: ['$anon_distinct_id', '$device_id', 'api_key', 'distinct_id', 'timezone', 'person_properties']

request_payload.disable_geoip_omitted_defaults_to_false

Field 'geoip_disable' not found in /flags request body at path 'geoip_disable'. Available keys: ['$anon_distinct_id', '$device_id', 'api_key', 'distinct_id', 'timezone', 'person_properties']

request_payload.flag_keys_to_evaluate_contains_only_requested_key

Field 'flag_keys_to_evaluate' not found in /flags request body at path 'flag_keys_to_evaluate'. Available keys: ['$anon_distinct_id', '$device_id', 'api_key', 'distinct_id', 'timezone', 'person_properties']

…n init

Declare capabilities ["capture_v0", "encoding_gzip"] in /health so the harness
runs the capture suites (android posts events to /batch and gzips them).
Also set remoteConfig = false in /init so tests don't depend on the remote
config load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@turnipdabeets turnipdabeets marked this pull request as ready for review May 29, 2026 17:39
@turnipdabeets turnipdabeets requested a review from a team as a code owner May 29, 2026 17:39
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 29, 2026

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt:352-354
**`ph.group()` fires an extra `$groupidentify` event and may trigger a second `/flags` request**

`PostHog.group()` calls `groupStateless()` internally, which calls `captureStateless(GROUP_IDENTIFY, ...)`, queuing a `$groupidentify` event. More critically, because `PostHog.reloadFeatureFlags: Boolean = true` by default, it also calls `reloadFeatureFlags(config?.onFeatureFlags)` whenever the group key is new. Since `/reset` clears persisted groups between tests, every test run sees a fresh group — making `reloadFeatureFlagsIfNewGroup` true every time. This means two `/flags` requests get made (one from `group()`, one from the explicit `reloadFeatureFlags { latch.countDown() }` below), and one `$groupidentify` batch request is created before the latch is even started. If the harness asserts exact batch or `/flags` request counts for group flag tests, those tests will remain red despite this fix.

Reviews (1): Last reviewed commit: "feat(compliance): declare capture capabi..." | Re-trigger Greptile

Comment on lines +352 to +354
req.groups?.forEach { (type, key) ->
ph.group(type, key, req.group_properties?.get(type))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 ph.group() fires an extra $groupidentify event and may trigger a second /flags request

PostHog.group() calls groupStateless() internally, which calls captureStateless(GROUP_IDENTIFY, ...), queuing a $groupidentify event. More critically, because PostHog.reloadFeatureFlags: Boolean = true by default, it also calls reloadFeatureFlags(config?.onFeatureFlags) whenever the group key is new. Since /reset clears persisted groups between tests, every test run sees a fresh group — making reloadFeatureFlagsIfNewGroup true every time. This means two /flags requests get made (one from group(), one from the explicit reloadFeatureFlags { latch.countDown() } below), and one $groupidentify batch request is created before the latch is even started. If the harness asserts exact batch or /flags request counts for group flag tests, those tests will remain red despite this fix.

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk_compliance_adapter/src/main/kotlin/com/posthog/compliance/ComplianceAdapter.kt
Line: 352-354

Comment:
**`ph.group()` fires an extra `$groupidentify` event and may trigger a second `/flags` request**

`PostHog.group()` calls `groupStateless()` internally, which calls `captureStateless(GROUP_IDENTIFY, ...)`, queuing a `$groupidentify` event. More critically, because `PostHog.reloadFeatureFlags: Boolean = true` by default, it also calls `reloadFeatureFlags(config?.onFeatureFlags)` whenever the group key is new. Since `/reset` clears persisted groups between tests, every test run sees a fresh group — making `reloadFeatureFlagsIfNewGroup` true every time. This means two `/flags` requests get made (one from `group()`, one from the explicit `reloadFeatureFlags { latch.countDown() }` below), and one `$groupidentify` batch request is created before the latch is even started. If the harness asserts exact batch or `/flags` request counts for group flag tests, those tests will remain red despite this fix.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Accurate, thanks. There's no way around this with the public android API: group() is the only way to put a group into the /flags body, and it unconditionally emits $groupidentify and reloads flags on a new group — there's no suppression parameter (unlike setPersonPropertiesForFlags(reloadFeatureFlags = false), which the adapter does use for person properties). So the extra /flags + $groupidentify are inherent to using the public API for groups. It only affects the group-bearing flag tests (groups_round_trip), which are already red because /init fires a /flags regardless on the SDK side (every flag count is +1), so suppressing this wouldn't flip the test. Leaving the adapter on the public group() API.

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.

Is setGroupPropertiesForFlags not viable here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

addressed in 7adafd3


// Flush that event and wait so it can't spill into the next test's mock window.
ph.flush()
Thread.sleep(2000)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do we need this? Is there a better approach, like using a latch, waiting for the flush, or something?
Sleep slows down tests

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

agreed, but tried this on iOS and it regressed 6 tests.

@marandaneto
Copy link
Copy Markdown
Member

bsed on #542 (comment) that didnt work?

@marandaneto marandaneto requested a review from a team May 29, 2026 18:27
Use setGroupPropertiesForFlags(type, props, reloadFeatureFlags = false) for
group_properties (no extra reload), matching the iOS adapter pattern. group()
is still used for the type->key registration since the /flags `groups` field
reads from the GROUPS preference, which only group() writes — its
$groupidentify event and reload-on-new-group remain (no suppression param).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment on lines +249 to +250
// Don't load remote config on init either — it chains into a /flags
// request, which would add a spurious request to every test's count
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.

Not sure if this is the case cause even remoteConfig should only chain /flags call if preloadFeatureFlags = true. If it doesn't then I think this should be fixed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 639d0cd by swapping to SDK.close() in the adapter's /reset, matching the iOS adapter.

…r-get-feature-flag

# Conflicts:
#	pnpm-workspace.yaml
@turnipdabeets turnipdabeets force-pushed the fix/compliance-adapter-get-feature-flag branch from bfcf6e8 to 0f59b4e Compare June 2, 2026 14:57
SDK.reset() reloads flags as anon (PostHog.kt:1519 — intended behavior for
real-app logout), but that /flags lands in the next test's mock window and
inflates flag counts ("Expected 0, got 1" on no_flags_request_on_init_alone
and friends). close() has no network I/O; the next /init builds a fresh
instance. Matches the iOS adapter's pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@turnipdabeets turnipdabeets force-pushed the fix/compliance-adapter-get-feature-flag branch from e0fa4d5 to 639d0cd Compare June 2, 2026 23:12
Per review: the remoteConfig=false in /init claimed to stop a /flags reload,
but PostHogRemoteConfig.kt:243 only chains to /flags when preloadFeatureFlags
is true — so the config does nothing observable here. Dropping it.

Also trims the capabilities and group() comments to one line each.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@turnipdabeets
Copy link
Copy Markdown
Contributor Author

bsed on (earlier comment) that didnt work?

For some reason I can't see this comment

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.

4 participants