Skip to content

feat: add getRemoteConfigPayload to read a remote config flag payload - #320

Open
ioannisj wants to merge 5 commits into
mainfrom
feat/get-remote-config-payload
Open

ioannisj wants to merge 5 commits into
mainfrom
feat/get-remote-config-payload

Conversation

@ioannisj

@ioannisj ioannisj commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Part of PostHog/posthog-android#577.

posthog-php had no way to read the payload of a remote config feature flag. posthog-node has getRemoteConfigPayload() for this, and this adds the same method to PostHog\Client, with a static PostHog::getRemoteConfigPayload() forwarder:

$config = PostHog::getRemoteConfigPayload('landing-page-config');

It sends one GET {host}/api/projects/@current/feature_flags/{key}/remote_config?token={projectToken} with the secret key as a bearer token, and returns the decoded payload. The endpoint can answer with a JSON string that itself holds JSON, so a string result is decoded a second time, and returned as is when that second decode fails.

A few things that aren't obvious from the diff:

  • It never throws. It logs a warning and returns null when the client is disabled, no secret key is set, the request fails, PostHog answers with a non-2xx status, or the body is not valid JSON.
  • A 2xx answer with an empty or whitespace-only body means the flag has no payload, so it returns null without a warning. PostHog answers a flag with no payload that way. posthog-node throws a SyntaxError on this response today (await response.json() on an empty body, packages/node/src/client.ts:2272 at 480395c4), so this is one more place where the port differs from posthog-node. An empty body with a non-2xx status, like the 404 for an unknown flag, is still a failure.
  • The flag key is rawurlencoded as one path segment, and so is the token. A key that is empty, . or .. is refused before any request, because it would resolve to a different path.
  • No retry, and no $feature_flag_called event. The timeout is the existing feature_flag_request_timeout_ms option, 3 seconds by default.
  • The request goes to options['host'] through the client's existing HttpClient, the same as the other flag calls.

packages/laravel is unchanged. Its forwarders are declared explicitly, so the new method isn't exposed there, and that's worth a follow-up if we want it on the Laravel manager.

Public API change: api/public-api.json gains the two getRemoteConfigPayload entries.

💚 How did you test it?

  • New test/RemoteConfigPayloadTest.php, 29 tests against MockedHttpClient: the decode paths (object, double-encoded string, plain string, JSON null, non-JSON), an empty and a whitespace-only 200 body with no warning, a 404 with an empty body, the guards (disabled client, no secret key, refused keys), non-2xx statuses and curl failures with no retry, the encoding of the key and the token, the request headers and options, the configured timeout, and the static forwarder.
  • The full PHPUnit suite passes apart from EmailsClientTest::testAnOversizedReplyIsNotReadOrRetried, which fails on some local runs with and without this change. It uses the loopback server and this PR doesn't touch it.
  • phpcs reports 0 errors, and composer api:check and composer api:check:laravel both report an up-to-date snapshot.
  • Not tested against a live PostHog project. Every request in the tests is mocked.

📝 Checklist

  • All commits are signed (see Commit signing)
  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm change to generate a change intent file

🤖 Agent context

DRI: @ioannisj
Autonomy: Human-driven (agent-assisted)

@ioannisj ioannisj self-assigned this Nov 13, 2026
@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

posthog-php-socket Compliance Report

Date: 2026-11-13T20:57:45.225562+00:00
Duration: 115373ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 25ms
Format Validation.Event Has Uuid ✅ 520ms
Format Validation.Event Has Lib Properties ✅ 522ms
Format Validation.Distinct Id Is String ✅ 521ms
Format Validation.Token Is Present ✅ 521ms
Format Validation.Custom Properties Preserved ✅ 521ms
Format Validation.Event Has Timestamp ✅ 521ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 521ms
Retry Behavior.Retries On 503 ✅ 5830ms
Retry Behavior.Does Not Retry On 400 ✅ 2522ms
Retry Behavior.Does Not Retry On 401 ✅ 2524ms
Retry Behavior.Respects Retry After Header ❌ 6330ms
Retry Behavior.Implements Backoff ✅ 16236ms
Retry Behavior.Retries On 500 ✅ 5135ms
Retry Behavior.Retries On 502 ✅ 5633ms
Retry Behavior.Retries On 504 ✅ 5627ms
Retry Behavior.Max Retries Respected ✅ 16246ms
Deduplication.Generates Unique Uuids ✅ 527ms
Deduplication.Preserves Uuid On Retry ✅ 5631ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10841ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5632ms
Deduplication.No Duplicate Events In Batch ✅ 525ms
Deduplication.Different Events Have Different Uuids ✅ 523ms
Compression.Sends Gzip When Enabled ✅ 521ms
Batch Format.Uses Proper Batch Structure ✅ 521ms
Batch Format.Flush With No Events Sends Nothing ✅ 517ms
Batch Format.Multiple Events Batched Together ✅ 509ms
Error Handling.Does Not Retry On 403 ✅ 2523ms
Error Handling.Does Not Retry On 413 ✅ 2523ms
Error Handling.Retries On 408 ✅ 5629ms

Failures

retry_behavior.respects_retry_after_header

Retry delay too short: 803ms < 2500ms

Feature_Flags Tests

✅ 17/17 tests passed

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

@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

posthog-php-fork_curl Compliance Report

Date: 2026-11-13T20:57:26.116230+00:00
Duration: 116669ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 41ms
Format Validation.Event Has Uuid ✅ 532ms
Format Validation.Event Has Lib Properties ✅ 534ms
Format Validation.Distinct Id Is String ✅ 534ms
Format Validation.Token Is Present ✅ 535ms
Format Validation.Custom Properties Preserved ✅ 535ms
Format Validation.Event Has Timestamp ✅ 534ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 535ms
Retry Behavior.Retries On 503 ✅ 5862ms
Retry Behavior.Does Not Retry On 400 ✅ 2537ms
Retry Behavior.Does Not Retry On 401 ✅ 2536ms
Retry Behavior.Respects Retry After Header ❌ 6355ms
Retry Behavior.Implements Backoff ✅ 16285ms
Retry Behavior.Retries On 500 ✅ 5652ms
Retry Behavior.Retries On 502 ✅ 5653ms
Retry Behavior.Retries On 504 ✅ 5652ms
Retry Behavior.Max Retries Respected ✅ 16286ms
Deduplication.Generates Unique Uuids ✅ 543ms
Deduplication.Preserves Uuid On Retry ✅ 5651ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10870ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5656ms
Deduplication.No Duplicate Events In Batch ✅ 541ms
Deduplication.Different Events Have Different Uuids ✅ 538ms
Compression.Sends Gzip When Enabled ✅ 537ms
Batch Format.Uses Proper Batch Structure ✅ 535ms
Batch Format.Flush With No Events Sends Nothing ✅ 522ms
Batch Format.Multiple Events Batched Together ✅ 522ms
Error Handling.Does Not Retry On 403 ✅ 2539ms
Error Handling.Does Not Retry On 413 ✅ 2537ms
Error Handling.Retries On 408 ✅ 5654ms

Failures

retry_behavior.respects_retry_after_header

Retry delay too short: 811ms < 2500ms

Feature_Flags Tests

✅ 17/17 tests passed

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

@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

posthog-php-lib_curl Compliance Report

Date: 2026-11-13T20:57:50.375907+00:00
Duration: 116646ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 29ms
Format Validation.Event Has Uuid ✅ 523ms
Format Validation.Event Has Lib Properties ✅ 526ms
Format Validation.Distinct Id Is String ✅ 526ms
Format Validation.Token Is Present ✅ 525ms
Format Validation.Custom Properties Preserved ✅ 525ms
Format Validation.Event Has Timestamp ✅ 526ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 526ms
Retry Behavior.Retries On 503 ✅ 5834ms
Retry Behavior.Does Not Retry On 400 ✅ 2529ms
Retry Behavior.Does Not Retry On 401 ✅ 2527ms
Retry Behavior.Respects Retry After Header ❌ 6336ms
Retry Behavior.Implements Backoff ✅ 16248ms
Retry Behavior.Retries On 500 ✅ 5636ms
Retry Behavior.Retries On 502 ✅ 5633ms
Retry Behavior.Retries On 504 ✅ 5634ms
Retry Behavior.Max Retries Respected ✅ 17050ms
Deduplication.Generates Unique Uuids ✅ 233ms
Deduplication.Preserves Uuid On Retry ✅ 5635ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10841ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5639ms
Deduplication.No Duplicate Events In Batch ✅ 531ms
Deduplication.Different Events Have Different Uuids ✅ 527ms
Compression.Sends Gzip When Enabled ✅ 526ms
Batch Format.Uses Proper Batch Structure ✅ 525ms
Batch Format.Flush With No Events Sends Nothing ✅ 520ms
Batch Format.Multiple Events Batched Together ✅ 515ms
Error Handling.Does Not Retry On 403 ✅ 2527ms
Error Handling.Does Not Retry On 413 ✅ 2528ms
Error Handling.Retries On 408 ✅ 5635ms

Failures

retry_behavior.respects_retry_after_header

Retry delay too short: 804ms < 2500ms

Feature_Flags Tests

✅ 17/17 tests passed

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

@posthog posthog Bot added the self-driving label Nov 13, 2026
@posthog

posthog Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

PostHog Review · Deep · reviewed 31707b8

Nothing to post.

Step Status Result
Prepare the diff done 4 files, 1 chunk
Pick perspectives done 3 perspectives
Run review passes done 3 issues
Merge overlapping findings done 3 → 3
Validate findings done 0 kept, 3 dismissed
Publish done Nothing to post

The dancing man in the red room from Twin Peaks

@ioannisj
ioannisj marked this pull request as ready for review November 13, 2026 14:23
@ioannisj
ioannisj requested a review from a team as a code owner November 13, 2026 14:23
@veria-ai

veria-ai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

Reviews

@posthog posthog Bot 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.

PostHog Review · Standard · reviewed 6114a8e: 1 Should fix.

See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.

Comment thread lib/Client.php Resolved
Comment thread lib/Client.php Resolved

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant