Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/posthog-no-implicit-local-evaluation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@flags-sdk/posthog": patch
---

Stop the default `postHogAdapter` from enabling local evaluation when `POSTHOG_PERSONAL_API_KEY` is set.

`POSTHOG_PERSONAL_API_KEY` is only needed for provider data discovery (`getProviderData`), which reads it independently. Passing it into the runtime `posthog-node` client also enabled local evaluation, which starts a per-process feature-flag poller (`featureFlagsPollingInterval: 10_000`). On serverless deployments each warm process ran its own poller, generating large numbers of PostHog feature flag requests unrelated to actual traffic.

The default adapter no longer forwards the personal API key or the polling interval to the runtime client. To use local evaluation, create the adapter explicitly with `createPostHogAdapter` and pass `personalApiKey` and `featureFlagsPollingInterval` in `postHogOptions`.
16 changes: 16 additions & 0 deletions packages/adapter-posthog/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ReadonlyHeaders, ReadonlyRequestCookies } from 'flags';
import { PostHog } from 'posthog-node';
import { beforeAll, describe, expect, it, vi } from 'vitest';
import { type PostHogEntities, postHogAdapter } from '.';

Expand Down Expand Up @@ -30,6 +31,9 @@ describe('postHogAdapter', () => {
beforeAll(() => {
process.env.NEXT_PUBLIC_POSTHOG_KEY = 'test-posthog-key';
process.env.NEXT_PUBLIC_POSTHOG_HOST = 'https://us.i.posthog.com';
// Present because provider data discovery needs it, but it must not leak
// into the runtime client. See issue #393.
process.env.POSTHOG_PERSONAL_API_KEY = 'phx_test_personal_api_key';
});

describe('isFeatureEnabled', () => {
Expand Down Expand Up @@ -90,5 +94,17 @@ describe('postHogAdapter', () => {
expect(postHogClientMock.getFeatureFlagPayload).toHaveBeenCalled();
});
});

// Regression test for issue #393: the default adapter must not forward
// `POSTHOG_PERSONAL_API_KEY` into the runtime `posthog-node` client, because
// that enables local evaluation and starts a per-process feature-flag poller.
describe('default runtime client options', () => {
it('should not enable local evaluation from POSTHOG_PERSONAL_API_KEY', () => {
expect(PostHog).toHaveBeenCalled();
const options = vi.mocked(PostHog).mock.calls[0]?.[1] ?? {};
expect(options).not.toHaveProperty('personalApiKey');
expect(options).not.toHaveProperty('featureFlagsPollingInterval');
});
});
});
});
8 changes: 6 additions & 2 deletions packages/adapter-posthog/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,12 @@ function getOrCreateDefaultAdapter() {
postHogKey: assertEnv('NEXT_PUBLIC_POSTHOG_KEY'),
postHogOptions: {
host: assertEnv('NEXT_PUBLIC_POSTHOG_HOST'),
personalApiKey: process.env.POSTHOG_PERSONAL_API_KEY,
featureFlagsPollingInterval: 10_000,
// `POSTHOG_PERSONAL_API_KEY` is only needed for provider data discovery
// (see `getProviderData`), which reads it independently of this client.
// Passing it here would enable local evaluation in `posthog-node`, which
// starts a per-process feature-flag poller. To use local evaluation,
// create the adapter explicitly with `createPostHogAdapter` and pass
// `personalApiKey`/`featureFlagsPollingInterval` in `postHogOptions`.
// Presumption: Server IP is likely not a good proxy for user location
disableGeoip: true,
},
Expand Down