Repository navigation
chore!: return null for malformed feature flag payloads - #889
Merged
turnipdabeets merged 1 commit intoOct 26, 2026
Merged
Conversation
4 of 5 tasks
turnipdabeets
force-pushed
the
chore/v4-remove-deprecated
branch
from
October 26, 2026 18:47
a41d9b9 to
caf64bd
Compare
dustinbyrne
approved these changes
Oct 26, 2026
Matches posthog-ios 4.0 and the sdk-specs payload decoding contract.
turnipdabeets
force-pushed
the
chore/v4-malformed-flag-payloads
branch
from
October 26, 2026 23:05
0f0bfc8 to
2bfd608
Compare
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.
Note
Stacked on the 4.0 integration PR #871. Merges into
chore/v4-remove-deprecated, notmain.💡 Motivation and Context
Brings Android in line with the sdk-specs payload decoding contract (
get-feature-flag-payload, "Malformed serialized payloads return no payload", plus the matchingget-feature-flag-resultandget-feature-flags-and-payloadsrequirements). Mirrors PostHog/posthog-ios#903 (iOS 4.0), which noted that Android needed its own change.{broken,[1, 2), empty or whitespace-onlynull, logged with the flag key"hello","",false,0, objects, arrays)null"null"null(the spec keeps JSON null, and Kotlin has one null)hello, single quotes, comments)null"123"/"true"StringString(unchanged)normalizePayloadsinPostHogRemoteConfignow decodes with a strictJsonReader(the shared serializer's Gson is lenient) and rejects trailing data. It runs on every/flagsresponse shape (v4flags, legacyfeatureFlagPayloads, and the partialerrorsWhileComputingFlagsmerge), sogetFeatureFlagResult,getFeatureFlagPayloadand the cache all see the same value./flagspayloads once when the response arrives and keeps decoded values in memory and in the cache; bootstrap payloads are served as passed. A new test locks this in (it passes before and after this change).Public API change: no signature changes; behavior change only (a line in
.changeset/android-v4-major.md).Edge case: the cache stores decoded payloads, and on 3.x a malformed payload was cached as its raw string. That entry is indistinguishable from a real string payload, so an app upgrading from 3.x still reads the raw string from the cache until its first
/flagsresponse replaces it. Telling them apart would need a cache format marker, which isn't worth it for this case.Scope:
posthog-serveris untouched; it gets its own major, and the spec's server local/remote evaluation rules belong there.getFeatureFlagResult(key)?.payloadthrough unchanged, so no code change; with this, Flutter readsnullfor a malformed payload on both platforms once it adopts Android 4.0 and iOS 4.0.💚 How did you test it?
PostHogRemoteConfigTest: malformed ({broken,""," ", unquotedhello, trailing data) payloads returnnullwhile the flag,getFeatureFlags()and a sibling payload are preserved, the parse failure is logged with the flag key, and a new instance reading the cache gets no payload either; valid JSON keeps its type ("hello","",false,0,null, object, array); bootstrapped"hello","123","true"stay strings.{brokencame back as the raw string, JSONnullas the string"null"); the bootstrap test passed, confirming there was no double decode.theBroken([1, 2) now expectsnull, and the quota-limited fixtures send"\"payload1\""(valid JSON, as the server does) instead of the unquotedpayload1../gradlew spotlessCheck apiCheck :posthog:test :posthog-android:testReleaseUnitTestpass (1067 core tests; 765 Android tests, 3 skipped).:posthog-server:testalso passes.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file (adds a line to.changeset/android-v4-major.md)🤖 Agent context
Autonomy: Human-driven (agent-assisted)
hellowould otherwise decode to"hello", which is the raw string the spec rules out. iOS'sJSONSerializationis strict, so this also keeps the two platforms on the same inputs.🤖 Generated with Claude Code