Files
Krzysztof CzerwinskiandClaude Opus 5.5 3c06724a3e refactor(platform): remove LaunchDarkly and read every flag from PostHog (#15853)
### Why / What / How

**Why:** LaunchDarkly gets users' email, email domain, role and signup
date, without consent. Dev and prod have served every flag from PostHog
since 16 Oct (infra #437/#439), and #15583 made PostHog the code
default, so LaunchDarkly no longer answers any flag. Removing it closes
gap 3 of the data-flow audit and drops a vendor. The team decided on 23
Sep to remove it entirely, experiments included. Ticket:
[SECRT-2708](https://linear.app/autogpt/issue/SECRT-2708).

**What:** Both LaunchDarkly SDKs go, along with the `launchdarkly` and
`dual` flag backends and the switch between them, on the backend and the
frontend. Flags behave exactly as they do today: the backend asks
PostHog with the same person properties, and the browser reads
`/api/feature-flags` as it already does everywhere.

**How:** The backend's LaunchDarkly `Context` was also the user context
PostHog's person properties were built from. It is now a small dataclass
with the same fields, cache and degraded-lookup rules. The backend sends
the same properties: `role`, `email`, `email_domain`, `created_at`, the
per-request `country`, `environment` and `is_anonymous`.

### Changes 🏗️

**Backend**
- Removed the `launchdarkly-server-sdk` dependency; `poetry.lock`
regenerated with Poetry 2.5.1. The `launchdarkly` extra on `sentry-sdk`
was already gone (#14805).
- `backend/util/feature_flag`:
  - Every read goes to PostHog.
- Removed: LaunchDarkly init/shutdown, `get_client`, the dual shadow
reads and mismatch log, and `mock_flag_variation`.
- Settings: removed `FeatureFlagBackend`, `feature_flag_backend` and
`launch_darkly_sdk_key`.
- Infra #452 (merged 23 Oct) already removed `LAUNCH_DARKLY_SDK_KEY` and
the flag-backend setting from prod. If any environment still sets either
variable, `Settings` allows extra keys, so it is ignored and boot is
unaffected. A test covers this.
- Deleted `scripts/feature_flag_sync.py` and
`scripts/sync_feature_flags_to_posthog.py` with their tests. They only
copied LaunchDarkly flags into PostHog.
- `country` (#14799) already reaches PostHog as the person property
`country`, per read and never stored.
- It stays `country`, not `$geoip_country_code`: server-side reads get
no GeoIP, and the trial-offer flag in PostHog targets `country`.
- An anonymous stand-in context (a failed user lookup) still sends no
country, as before.
- `.env.default`: removed `LAUNCH_DARKLY_SDK_KEY` and
`FEATURE_FLAG_BACKEND`.
- LaunchDarkly wording in comments and docstrings now says PostHog or
feature flag.
- Two messages Otto reads now say "feature flag" instead of
"LaunchDarkly flag".
  - Three OpenAPI descriptions changed; `openapi.json` was regenerated.

**Frontend**
- Removed `launchdarkly-react-client-sdk` (lockfile regenerated),
`LDProvider` and the LaunchDarkly context building, including the device
context from #14288.
- Removed the `NEXT_PUBLIC_FEATURE_FLAG_BACKEND` switch with its
`launchdarkly`, `dual` and client-side `posthog` paths. Every
environment runs `server`, so `useFlagSource` now always reads
`/api/feature-flags`.
- Removed `environment.areFeatureFlagsEnabled` /
`getLaunchDarklyClientId`, and the `setPersonPropertiesForFlags` call,
which only ran in the client-side `posthog`/`dual` modes.
- Retired the `feature_flag_mismatched` event, which only the dual
backend sent.
- Experiments: `useExperiment.ts` and `useReportAssignment.ts` stay on
PostHog (the LaunchDarkly experiment bridge was already deleted). The
browser's assignment `source` is now only `"posthog"`.
- `.env.default`: removed `NEXT_PUBLIC_LAUNCHDARKLY_ENABLED`,
`NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID` and
`NEXT_PUBLIC_FEATURE_FLAG_BACKEND`.
- Tests for the LaunchDarkly, dual and client-side PostHog paths are
removed. Tests for the remaining behaviour now mock the flag seam
instead of the LaunchDarkly SDK.

**Docs and image**
- Updated `docs/engineering/feature-flags.md`, `activation-metrics.md`,
`tracking-plan.md`, `docs/platform/contributing/managing-llm-models.md`,
`frontend/CONTRIBUTING.md` and the threat model.
- Single-container image: removed `NEXT_PUBLIC_LAUNCHDARKLY_ENABLED` and
the step that deleted `ldclient`'s bundled test certificates.

**Kept on purpose**
- The backend `AssignmentSource` still accepts `"launchdarkly"`, so
existing `experiment_assignment` rows recorded with it stay valid.
- The stored `routingSource` value `"ld"` is unchanged, because it is a
stored value.

**Config changes**
- Removed env vars: `LAUNCH_DARKLY_SDK_KEY`, `FEATURE_FLAG_BACKEND`
(backend); `NEXT_PUBLIC_LAUNCHDARKLY_ENABLED`,
`NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID`, `NEXT_PUBLIC_FEATURE_FLAG_BACKEND`
(frontend).
- Leftover values are ignored. Infra #452 already removed the
LaunchDarkly key and flag-backend setting from prod. Any remaining
`FEATURE_FLAG_BACKEND` or `NEXT_PUBLIC_FEATURE_FLAG_BACKEND` values in
infra or Vercel can be dropped whenever convenient.

**Rollback:** revert this PR and [infra
#452](https://github.com/Significant-Gravitas/AutoGPT_cloud_infrastructure/pull/452),
so `LAUNCH_DARKLY_SDK_KEY` and the Vercel LaunchDarkly vars come back.
Then set `FEATURE_FLAG_BACKEND=launchdarkly` (backend) and
`NEXT_PUBLIC_FEATURE_FLAG_BACKEND=launchdarkly` (frontend, needs a
rebuild). LaunchDarkly's flags must still match PostHog's, because
LaunchDarkly hasn't served since 16 Oct. Reverting this PR alone keeps
PostHog serving, since it is already the code default.

### Agents and large language models used

- Claude Code — Claude Opus 5.5

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [x] Backend flag unit tests (`backend/util/feature_flag/`,
`api/features/feature_flags/routes_test.py`,
`data/subscription_trial_config_test.py`, `util/metrics_test.py`) pass
locally, including real PostHog clients evaluating the country rule, the
employee cohort and the visitor split in-process
- [x] Frontend `pnpm lint`, `pnpm types` and `pnpm test:unit` pass
(flag, experiment, PostHog provider and affected page tests)
- [x] `openapi.json` regenerated; `poetry.lock` and `pnpm-lock.yaml`
regenerated
  - [ ] CI green
- [ ] After merge and deploy: flags on dev and prod still match today's
(e.g. `/api/feature-flags` for an employee and for a signed-out visitor)

#### For configuration changes:

- [x] `.env.default` is updated or already compatible with my changes
- [x] `docker-compose.yml` is updated or already compatible with my
changes
- [x] I have included a list of my configuration changes in the PR
description (under **Changes**)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-02 14:59:19 +00:00
..