Skip to content

chore!: iOS Major Release 4.0 - #873

Draft
turnipdabeets wants to merge 83 commits into
mainfrom
chore/v4-remove-deprecated
Draft

turnipdabeets wants to merge 83 commits into
mainfrom
chore/v4-remove-deprecated

Conversation

@turnipdabeets

@turnipdabeets turnipdabeets commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Important

This is the posthog-ios 4.0 integration PR. Don't merge it until every item under "Before release" is done. It targets main without changesets pre mode, so merging it publishes 4.0.0. "Release day" and "After release" items don't block the merge. Every stacked 4.0 PR merges into this branch (chore/v4-remove-deprecated), and this PR brings them into main at once. The branch carries one changeset, .changeset/v4-major-release.md.

Closes #557

💡 Motivation and Context

posthog-ios 4.0:

  • Analytics events go to capture v1 (/i/v1/analytics/events) with Authorization: Bearer, and the new captureAi sends AI events to /i/v1/ai/events. There's no /batch fallback.
  • Per-event capture options, and one property precedence: SDK-set, then super properties, then event properties, then beforeSend.
  • APIs deprecated in 3.x are removed, the feature flags callbacks change, and session replay always records masked screenshots.
  • Swift Package Manager only, with iOS 15, macOS 11, tvOS 15 and watchOS 10 minimums and Xcode 26. Replay, crash reporting and surveys are package traits you can turn off.

The upgrade guide is MIGRATION.md, sorted into Before, During and Follow ups. It's also served at /docs/libraries/ios/migration.

Public API change: breaking, see the api/posthog-ios.public-api.txt diff. Removed and renamed 3.x APIs fail to compile with an error naming the replacement (#977).

Docs and onboarding that ship with this release (merge once 4.0.0 is tagged on GitHub):

What gets released

Artifact Bump Notes
posthog-ios (Swift Package Manager) 3.97.0 → 4.0.0 The tag the release workflow pushes.
PostHog pod (CocoaPods) not released 4.x isn't published to CocoaPods (#972). CocoaPods trunk goes read-only on 2026-12-02, so a 4.x pod would stop updating within weeks, and pod builds always compile every trait in. 3.x stays on CocoaPods. The README, changeset and guide say so.

Wrappers stay on 3.x until their own majors adopt 4.0: posthog_flutter main pins < 4.0.0, the React Native plugin pins ~> 3.92.7, and both import posthog-ios with @_spi(PostHogInternal).

Wrapper Release PR Adopts posthog-ios 4.0 Docs
posthog_flutter 6.0 PostHog/posthog-flutter#637 PostHog/posthog-flutter#649 (merged into feat/v6-desktop-capture-v1) PostHog/posthog.com#20924
@posthog/react-native-plugin 3.0 PostHog/posthog-js#5194 PostHog/posthog-js#5237, #5236 (drops screenshotMode) and #5449 (SPM by default), merged into the release branch; PostHog/posthog-js#5734 (captureMethod) is open PostHog/posthog.com#20922
posthog-kmp PostHog/posthog-kmp#108 same PR

React Native cost of SPM only: the plugin's minimum React Native rises from 0.71 to 0.75 (the first with spm_dependency), and its 3.0 makes SPM the default (#5449). No Podfile linkage or Expo change is needed. posthog-js#5237 turns the Surveys trait off in the plugin's Package.swift, but the plugin still sets config.surveys = false, which only compiles with the trait. Guard that line with a define only the plugin's Package.swift sets, since spm_dependency can't pass traits.

Merged into this branch

PR Change
this PR's first commit Remove deprecated APIs: apiKey, propertiesSanitizer, evaluationEnvironments, remoteConfig, getFeatureFlagPayload(_:), debouncerDelay, maskPhotoLibraryImages
#876, #906 iOS 15, macOS 11, tvOS 15 and watchOS 10 minimums
#898 Xcode 26 / swift-tools-version: 6.2; Package@swift-5.9.swift removed
#916, #975, #981, #968 SessionReplay, CrashReporting and Surveys traits, on by default; a trait that's off also removes its public API, snapshotted in CI
#874 Wrapper-only APIs behind @_spi(PostHogInternal)
#890 Remove PostHogDataMode.cellular
#912 Rename console log types to PostHogConsoleLog*
#875 Drop 2.x queue files and their top-level $set; 3.x migrations are kept
#905 Drop legacy version and build from lifecycle events
#913 reloadFeatureFlags callback receives a PostHogFeatureFlagsLoaded
#903 Malformed flag payloads return nil
#909 Replace the vendored Reachability with NWPathMonitor
#907, #973 Remove wireframe replay capture and screenshotMode
#937, #1052 Remove, then restore screenshotModeBackgroundCapture (still experimental)
#965 Remove the didReceiveFeatureFlags notification; use onFeatureFlags(_:)
#974 setBeforeSend(_:) accepts plain closure arrays instead of trapping at runtime
#976 Replay masks cover content drawn outside a masked view's bounds (also released in 3.93.1, so not in the changeset)
#977 unavailable fix-its for removed and renamed APIs
#1001 isFeatureEnabled(_:defaultValue:)
#1027 setSdkName(_:version:) (SPI, Objective-C) for wrapper SDKs
#1045 registerOptionsForWrapper(_:) / unregisterOptionsForWrapper(_:) (SPI, Objective-C)
#938 Capture v1, per-event options, property precedence, captureAi (#939), no /batch fallback (#940), $locale/$app_language (#1009), maxRetries → pushNotificationSubscriptionMaxRetries, and the capture v1 fixes (#1018, #1019, #1025, #1026)
#1036, #1050 Keep $lib and $lib_version per event, filled last and as a pair; skip the person-properties hash for updates that may be personless
#1037 An empty-string variant reads as disabled
#1047 Omit NSNull and nil event properties
#1067, #1053 A registered $process_person_profile no longer applies to $identify, $create_alias, $merge_dangerously, $groupidentify, $set and $set_once
#972 SPM-only release, RELEASING.md and the CocoaPods notice
#910 Text and names left stale by the 4.0 changes
#978, #980, #1061 MIGRATION.md for all of 4.0, sorted by when you make each change, plus docs/getting-started.md for the generated reference

main is merged in up to 3.96.2 plus #911 and #966 (experimental GPU capture, off by default): #1059 (failed flag records), #1063 (background-capture masks), #1042 and #1043 (surveys). 3.x changesets were released from main, so they aren't repeated in the 4.0 changeset. Where they met 4.0, 4.0's shape is kept: a failed flag's $feature_flag_called omits $feature_flag_response instead of sending null (06d91a9e8), which the guide's "Check null properties" covers.

main is then merged up to 3.97.0 (ef45d7b59): only the release commit, whose screenshotModeGPUCapture changeset was released from main. The podspec stays deleted.

Left in place on purpose:

  • The internal PostHogEvent.apiKey and PostHogRemoteConfig.getFeatureFlagPayload, and PostHogConfig(apiKey:) in scripts/downgrade-compatibility-smoke, which compiles against 3.48.0.
  • The storage migrations migrateLegacyStorage (3.20) and mergeLegacyContainerIfNeeded (3.27). Removing them would lose the distinct ID and queued events for apps updating straight from an older version: in the last 7 days (US), about 8% of iOS SDK end users were on an app build older than 3.27. We'll revisit in a later major.

sdk-specs: 4.0 implements the capture v1 contract (PostHog/sdk-specs#146, #147) and the property precedence from PostHog/sdk-specs#143. #938 lists where iOS differs from the spec. PostHog/sdk-specs#187 (4.0 compliance audit) is merged.

Before release

Release day, after 4.0.0 is tagged

After release

Found in testing, not 4.0 regressions

The 2026-11-05 device run turned up three things that behave the same in 3.93.6. None blocks 4.0:

  • Session replay records only the key window, and surveys show in their own window above it, so a survey popover never appears in a recording.
  • optOut() then optIn() logs "Failed to enable PHPLCrashReporter: A PLCrashReporter instance has already been enabled". Crash reporting keeps working.
  • identify(_:) on an already identified user with a different ID is ignored until reset(), as in 3.x.

💚 How did you test it?

2026-11-12, iPhone 17 Pro simulator (iOS 26.4), at 79325748a, through a logging proxy to PostHog Cloud (US), covering what merged after the 2026-11-05 run:

2026-11-05: all runs at 91118b1 (after #938 and #1027 merged). The two commits after it change only docs and the harness pin.

CI: every check is green on 91118b1: build (iOS, macOS, tvOS, watchOS, visionOS, SPM, no-traits, minimum toolchain Xcode 26.0.1), test, test-no-traits, the three test-ios-simulator jobs, replay masking snapshots, public API snapshot, examples, downgrade compatibility (3.48.0, 3.48.3, 3.58.0), lint.

SDK compliance harness: 234/234 on harness 1.26.0 in CI (all capture v1, AI and flags suites). Locally on harness 1.28.0, the latest release: also 234/234, so this PR bumps the pin to 1.28.0. 1.27.0 and 1.28.0 only touch feature flag rules v2 contracts and add a feature_flags_v4 suite that iOS doesn't opt into.

End to end against PostHog Cloud (US), on an iPhone 14 Pro Max (iOS 26.5.2) and an iPhone 17 simulator (iOS 26.4). A scripted probe in the example app (behind a launch argument, not committed) drove the SDK; every result below was checked in the ingested data with SQL, not just in the app log.

Area What was checked Result
Upgrade from 3.93.6 The 3.93.6 example app identified a user and queued 21 events against an unreachable host. 4.0 installed over it on the same storage. All 21 events arrived with their 3.x session ID, distinct ID and super property. The distinct and anonymous IDs carried over. Application Updated has previous_version 3.93. A 3.x event with $process_person_profile: false arrived as full, because 3.93.6 had already stored true for an identified user.
Capture v1 capture with properties, timestamp: (−1 h), options: ["process_person_profile": false], the legacy $process_person_profile property, screen, identify, alias, group, setPersonProperties, register/unregister, a 300 KB event, a 120-event burst All arrived. The backdated timestamp is stored, both personless forms are stored as propertyless, nested number and bool keys arrive as strings, super properties apply and stop after unregister.
Dropped events beforeSend returning nil, a 250-byte distinct ID, capture while opted out None arrived. PostHog reported distinct_id_too_large: 1 in the per-event results. A beforeSend edit arrived.
captureAi $ai_generation on /i/v1/ai/events Arrived; the stored UUID matches the one captureAi returned.
$locale / $app_language Default, setAppLanguage("fr-CA"), setAppLanguage(nil) $locale is en-US (3.x sent en). $app_language is en, fr-CA after the override, and en after the reset.
Feature flags Boolean and multivariate flags with payloads, a missing flag, isFeatureEnabled(_:defaultValue:), setPersonPropertiesForFlags with reloadFeatureFlags { result in }, onFeatureFlags Values and payloads correct. A missing flag returns the default passed. The reload callback reports the new flag set, and a person-property-gated flag turns on. $feature_flag_called and $experiment_exposure arrived.
Lifecycle Install, update, open, background, foreground Application Installed, Updated, Opened and Backgrounded arrived, without version and build.
Error tracking captureException (handled) and two Swift runtime traps, one of them after optOut()/optIn() The handled error arrived as error. Each crash arrived as a fatal, unhandled $exception with the Swift message on the next launch.
Logs captureLog Arrived in Logs with its attributes, distinct ID and screen name.
Surveys An event-triggered popover survey survey shown arrived for the survey on device and simulator.
Session replay Recording through a screen with a plain label, a ph-no-capture label, a text field, a secure field and an image; the uploads were captured through a local proxy and decoded Recordings ingested (mobile, posthog-ios). Text, both text fields and the masked label are masked; the SF Symbol stays visible, which is intended. Network and console plugins start from remote config.
Reachability (NWPathMonitor) Device only, toggling Airplane Mode and Wi-Fi: going offline in the foreground, offline in the background with the reconnect while backgrounded, and launching while offline The queue pauses while unreachable and sends the backlog once the path is satisfied. 40/40, 12/12 and 47 queued events arrived without gaps.

Not run: make test locally (CI covers it on this head), the macOS, tvOS, watchOS and visionOS apps on devices (CI builds them; #938 ran macOS end to end), and touch autocapture ($autocapture) on device.

Since that run: CI is green on 06d91a9e8 (48 checks), which adds #1061 (docs), #1067 (registered personless setting off identity events), main up to 3.96.2 (#1059, #1063, #1042, #1043) and the experimental GPU capture (#911, #966). The device runs weren't repeated on it.

📝 Checklist

  • 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 changeset to generate a changeset file

The changeset is major and was written by hand. Its 3.x-only entries were released from main, so they aren't repeated. The release workflow runs on main, so merging this PR publishes 4.0.0.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Claude Code (Opus 5.5) wrote the stacked PRs and ran the release reviews and device tests above. The end-to-end probe and the decoded replay frames stay local and aren't committed.

🤖 Generated with Claude Code

@turnipdabeets turnipdabeets self-assigned this Sep 30, 2026

@greptile-apps greptile-apps 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.

This review was skipped because it would exceed your organization's monthly flex usage limit. Raise the limit in billing settings or wait until the next billing period resets limits.

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

posthog-ios Compliance Report

Date: 2026-11-15T03:08:41.275357+00:00
Duration: 957253ms

✅ All Tests Passed!

234/234 tests passed


Capture_V1 Tests

✅ 106/106 tests passed

View Details
Test Status Duration
Endpoint And Method.Targets V1 Endpoint ✅ 3245ms
Endpoint And Method.Does Not Use Legacy Endpoints ✅ 2789ms
Required Headers.Has Authorization Bearer Header ✅ 2836ms
Required Headers.Has Content Type Json ✅ 2740ms
Required Headers.Has Posthog Sdk Info Format ✅ 2749ms
Required Headers.Has Posthog Attempt Header ✅ 2713ms
Required Headers.Has Posthog Request Id ✅ 414ms
Required Headers.Has Posthog Request Timestamp ✅ 2762ms
Required Headers.Has User Agent ✅ 2827ms
Body Format.Body Has Created At And Batch ✅ 2782ms
Body Format.No Api Key In Body ✅ 2805ms
Body Format.No Sent At In Body ✅ 2748ms
Event Format.Event Has Required Root Fields ✅ 2700ms
Event Format.Event Uuid Is Valid ✅ 2791ms
Event Format.Event Timestamp Is Rfc3339 ✅ 2835ms
Event Format.Non Utc Event Timestamp Is Converted To Utc ✅ 2847ms
Event Format.Distinct Id Is String ✅ 2814ms
Event Format.Distinct Id At Root Not Properties ✅ 2739ms
Event Format.Custom Properties Preserved ✅ 2723ms
Event Format.Set Properties Preserved ✅ 2874ms
Event Format.Set Once Properties Preserved ✅ 446ms
Event Format.Groups Properties Preserved ✅ 2830ms
Event Format.Sdk Generates Uuid If Not Provided ✅ 329ms
Event Format.Event Has Required Root Fields Batch ✅ 2838ms
Event Format.Event Uuid Is Valid Batch ✅ 2918ms
Event Format.Event Timestamp Is Rfc3339 Batch ✅ 3014ms
Event Format.Distinct Id Is String Batch ✅ 3131ms
Event Format.Distinct Id At Root Not Properties Batch ✅ 604ms
Event Format.Custom Properties Preserved Batch ✅ 2852ms
Event Format.Set Properties Preserved Batch ✅ 2712ms
Event Format.Set Once Properties Preserved Batch ✅ 433ms
Event Format.Groups Properties Preserved Batch ✅ 2832ms
Event Format.Sdk Generates Uuid If Not Provided Batch ✅ 2863ms
Batch Behavior.Multiple Events In Single Batch ✅ 2844ms
Batch Behavior.Batch Envelope Smoke ✅ 551ms
Batch Behavior.Flush With No Events Sends Nothing ✅ 301ms
Batch Behavior.Flush At Triggers Batch ✅ 1239ms
Batch Behavior.Created At Reflects Batch Creation Time ✅ 2881ms
Deduplication.Generates Unique Uuids ✅ 2814ms
Deduplication.Different Events Same Content Different Uuids ✅ 2806ms
Deduplication.Preserves Uuid On Retry ✅ 9171ms
Deduplication.Preserves Timestamp On Retry ✅ 9369ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 7787ms
Deduplication.No Duplicate Events In Batch ✅ 2947ms
Header Behavior On Retry.Attempt Header Starts At One ✅ 2708ms
Header Behavior On Retry.Attempt Header Increments On Retry ✅ 14331ms
Header Behavior On Retry.Request Id Preserved On Retry ✅ 9354ms
Header Behavior On Retry.Different Requests Have Different Request Ids ✅ 7429ms
Header Behavior On Retry.Request Timestamp Changes On Retry ✅ 9225ms
Response Format Validation.Success Response Has Uuid Keyed Results ✅ 2811ms
Response Format Validation.Success Response Has Ok For Each Event ✅ 2848ms
Response Format Validation.Success No Retry After When All Ok ✅ 2820ms
Response Format Validation.Success Retry After Present When Retry Events ✅ 2800ms
Response Format Validation.Success No Retry After When Drop Only ✅ 2881ms
Response Format Validation.Response Echoes Request Id ✅ 2806ms
Retry Behavior.Retries On 408 ✅ 9216ms
Retry Behavior.Retries On 500 ✅ 9271ms
Retry Behavior.Retries On 503 ✅ 11728ms
Retry Behavior.Retries On 504 ✅ 9359ms
Retry Behavior.Retries On 404 ✅ 9180ms
Retry Behavior.Retries On 429 ✅ 8870ms
Retry Behavior.Retries On 502 ✅ 8798ms
Retry Behavior.Retryable Errors Have Retry After ✅ 6293ms
Retry Behavior.Respects Retry After On Retryable Error ✅ 10743ms
Retry Behavior.Does Not Retry On 400 ✅ 4726ms
Retry Behavior.Does Not Retry On 401 ✅ 4966ms
Retry Behavior.Does Not Retry On 402 ✅ 4671ms
Retry Behavior.Does Not Retry On 413 ✅ 4761ms
Retry Behavior.Does Not Retry On 415 ✅ 4875ms
Retry Behavior.Non Retryable Errors Have No Retry After ✅ 4793ms
Retry Behavior.Implements Backoff ✅ 17963ms
Partial Batch Handling.Handles 200 Full Success ✅ 4798ms
Partial Batch Handling.Handles 200 With All Ok ✅ 5772ms
Partial Batch Handling.Does Not Retry Dropped Events ✅ 5786ms
Partial Batch Handling.Does Not Retry Warning Events ✅ 5737ms
Partial Batch Handling.Prunes Ok Events On Partial Retry ✅ 7779ms
Partial Batch Handling.Prunes Dropped Events On Partial Retry ✅ 7712ms
Partial Batch Handling.Retries Only Retry Events From Partial ✅ 7800ms
Partial Batch Handling.Partial Retry Preserves Uuids ✅ 7889ms
Partial Batch Handling.Partial Retry Attempt Header Increments ✅ 7780ms
Partial Batch Handling.Partial Retry Request Id Preserved ✅ 7911ms
Partial Batch Handling.Respects Retry After On Partial ✅ 7953ms
Partial Batch Handling.Unknown Result Treated As Terminal ✅ 5856ms
Partial Batch Handling.Mixed Ok Drop Warning No Retry ✅ 5830ms
Compression.Sends Gzip Content Encoding ✅ 2827ms
Compression.No Content Encoding When Disabled ✅ 2868ms
Compression.Compressed Body Is Decompressible ✅ 410ms
Error Handling.Does Not Retry On Unknown 4Xx ✅ 4753ms
Event Options.Cookieless Mode Override ✅ 2827ms
Event Options.Disable Skew Correction Override ✅ 2798ms
Event Options.Process Person Profile Override ✅ 2679ms
Event Options.Product Tour Id Override ✅ 2688ms
Event Options.Unset Options Omitted ✅ 2748ms
Event Options.Options Override In Batch ✅ 339ms
Event Options.Unknown Option Passes Through ✅ 2784ms
Event Options.Option Value Is Not Converted ✅ 2799ms
Event Options.Lenient Bool Option Values Are Not Converted ✅ 2702ms
Event Options.Option Wins Over Legacy Property ✅ 2750ms
Event Options.Legacy Property Fills Unset Option ✅ 391ms
Event Options.Null Option Falls Back To Legacy Property ✅ 399ms
Property Layers.Caller Property Beats Registered Property ✅ 2746ms
Property Layers.Before Send Runs Last ✅ 354ms
Fail Open.Empty Api Key Sends Nothing ✅ 1269ms
Fail Open.Personal Api Key Sends Nothing ✅ 1343ms
Geoip And Historical Migration.Geoip Disable Injected Into Properties ✅ 2849ms
Geoip And Historical Migration.Historical Migration Absent By Default ✅ 2796ms

Capture_Ai_V1 Tests

✅ 111/111 tests passed

View Details
Test Status Duration
Endpoint And Method.Targets V1 Endpoint ✅ 2781ms
Endpoint And Method.Does Not Use Legacy Endpoints ✅ 2812ms
Endpoint And Method.Capture Does Not Reroute Ai Named Events ✅ 2868ms
Endpoint And Method.Capture Ai Does Not Reroute Non Ai Events ✅ 2876ms
Lane Isolation.Failing Ai Lane Does Not Delay Analytics ✅ 5429ms
Required Headers.Has Authorization Bearer Header ✅ 2696ms
Required Headers.Has Content Type Json ✅ 2796ms
Required Headers.Has Posthog Sdk Info Format ✅ 2646ms
Required Headers.Has Posthog Attempt Header ✅ 2809ms
Required Headers.Has Posthog Request Id ✅ 2711ms
Required Headers.Has Posthog Request Timestamp ✅ 2781ms
Required Headers.Has User Agent ✅ 416ms
Body Format.Body Has Created At And Batch ✅ 2838ms
Body Format.No Api Key In Body ✅ 400ms
Body Format.No Sent At In Body ✅ 389ms
Event Format.Event Has Required Root Fields ✅ 2685ms
Event Format.Event Uuid Is Valid ✅ 2835ms
Event Format.Event Timestamp Is Rfc3339 ✅ 2688ms
Event Format.Non Utc Event Timestamp Is Converted To Utc ✅ 2852ms
Event Format.Distinct Id Is String ✅ 316ms
Event Format.Distinct Id At Root Not Properties ✅ 2855ms
Event Format.Custom Properties Preserved ✅ 2752ms
Event Format.Set Properties Preserved ✅ 2727ms
Event Format.Set Once Properties Preserved ✅ 2926ms
Event Format.Groups Properties Preserved ✅ 2724ms
Event Format.Sdk Generates Uuid If Not Provided ✅ 2811ms
Event Format.Event Has Required Root Fields Batch ✅ 2838ms
Event Format.Event Uuid Is Valid Batch ✅ 2928ms
Event Format.Event Timestamp Is Rfc3339 Batch ✅ 2712ms
Event Format.Distinct Id Is String Batch ✅ 2785ms
Event Format.Distinct Id At Root Not Properties Batch ✅ 415ms
Event Format.Custom Properties Preserved Batch ✅ 2828ms
Event Format.Set Properties Preserved Batch ✅ 2885ms
Event Format.Set Once Properties Preserved Batch ✅ 2754ms
Event Format.Groups Properties Preserved Batch ✅ 2873ms
Event Format.Sdk Generates Uuid If Not Provided Batch ✅ 373ms
Batch Behavior.Multiple Events In Single Batch ✅ 2957ms
Batch Behavior.Batch Envelope Smoke ✅ 2915ms
Batch Behavior.Flush At Triggers Batch ✅ 1194ms
Batch Behavior.Created At Reflects Batch Creation Time ✅ 2715ms
Deduplication.Generates Unique Uuids ✅ 2997ms
Deduplication.Different Events Same Content Different Uuids ✅ 2860ms
Deduplication.Preserves Uuid On Retry ✅ 9273ms
Deduplication.Preserves Timestamp On Retry ✅ 5343ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 7898ms
Deduplication.No Duplicate Events In Batch ✅ 2875ms
Header Behavior On Retry.Attempt Header Starts At One ✅ 239ms
Header Behavior On Retry.Attempt Header Increments On Retry ✅ 10366ms
Header Behavior On Retry.Request Id Preserved On Retry ✅ 9246ms
Header Behavior On Retry.Different Requests Have Different Request Ids ✅ 7402ms
Header Behavior On Retry.Request Timestamp Changes On Retry ✅ 9221ms
Response Format Validation.Success Response Has Uuid Keyed Results ✅ 2744ms
Response Format Validation.Success Response Has Ok For Each Event ✅ 2789ms
Response Format Validation.Success No Retry After When All Ok ✅ 2765ms
Response Format Validation.Success Retry After Present When Retry Events ✅ 2807ms
Response Format Validation.Success No Retry After When Drop Only ✅ 2770ms
Response Format Validation.Response Echoes Request Id ✅ 2745ms
Retry Behavior.Retries On 408 ✅ 5267ms
Retry Behavior.Retries On 500 ✅ 9265ms
Retry Behavior.Retries On 503 ✅ 11178ms
Retry Behavior.Retries On 504 ✅ 9247ms
Retry Behavior.Retries On 404 ✅ 9264ms
Retry Behavior.Retries On 429 ✅ 8608ms
Retry Behavior.Retries On 502 ✅ 8799ms
Retry Behavior.Retryable Errors Have Retry After ✅ 6263ms
Retry Behavior.Respects Retry After On Retryable Error ✅ 10867ms
Retry Behavior.Does Not Retry On 400 ✅ 2402ms
Retry Behavior.Does Not Retry On 401 ✅ 4731ms
Retry Behavior.Does Not Retry On 402 ✅ 4712ms
Retry Behavior.Does Not Retry On 413 ✅ 4668ms
Retry Behavior.Does Not Retry On 415 ✅ 4720ms
Retry Behavior.Non Retryable Errors Have No Retry After ✅ 4780ms
Retry Behavior.Implements Backoff ✅ 17887ms
Partial Batch Handling.Handles 200 Full Success ✅ 2336ms
Partial Batch Handling.Handles 200 With All Ok ✅ 5733ms
Partial Batch Handling.Does Not Retry Dropped Events ✅ 5751ms
Partial Batch Handling.Does Not Retry Warning Events ✅ 5846ms
Partial Batch Handling.Prunes Ok Events On Partial Retry ✅ 7804ms
Partial Batch Handling.Prunes Dropped Events On Partial Retry ✅ 7787ms
Partial Batch Handling.Retries Only Retry Events From Partial ✅ 7753ms
Partial Batch Handling.Partial Retry Preserves Uuids ✅ 7798ms
Partial Batch Handling.Partial Retry Attempt Header Increments ✅ 7794ms
Partial Batch Handling.Partial Retry Request Id Preserved ✅ 7720ms
Partial Batch Handling.Respects Retry After On Partial ✅ 7748ms
Partial Batch Handling.Unknown Result Treated As Terminal ✅ 5748ms
Partial Batch Handling.Mixed Ok Drop Warning No Retry ✅ 5782ms
Compression.Sends Gzip Content Encoding ✅ 2699ms
Compression.No Content Encoding When Disabled ✅ 296ms
Compression.Compressed Body Is Decompressible ✅ 2718ms
Error Handling.Does Not Retry On Unknown 4Xx ✅ 4755ms
Event Options.Cookieless Mode Override ✅ 370ms
Event Options.Disable Skew Correction Override ✅ 2713ms
Event Options.Process Person Profile Override ✅ 2662ms
Event Options.Product Tour Id Override ✅ 2761ms
Event Options.Unset Options Omitted ✅ 2739ms
Event Options.Options Override In Batch ✅ 2780ms
Event Options.Unknown Option Passes Through ✅ 314ms
Event Options.Option Value Is Not Converted ✅ 2776ms
Event Options.Lenient Bool Option Values Are Not Converted ✅ 2779ms
Event Options.Option Wins Over Legacy Property ✅ 353ms
Event Options.Legacy Property Fills Unset Option ✅ 2780ms
Event Options.Null Option Falls Back To Legacy Property ✅ 389ms
Event Size.Event Over Ceiling Is Not Sent ✅ 7283ms
Event Size.Over Ceiling Drop Is Reported Or Logged Without Content ✅ 2721ms
Event Size.Batches Close Before Byte Target ✅ 8652ms
Geoip And Historical Migration.Geoip Disable Injected Into Properties ✅ 2799ms
Geoip And Historical Migration.Historical Migration Absent By Default ✅ 2690ms
Property Layers.Caller Property Beats Registered Property ✅ 2767ms
Property Layers.Before Send Runs Last ✅ 2793ms
Fail Open.Empty Api Key Sends Nothing ✅ 1253ms
Fail Open.Personal Api Key Sends Nothing ✅ 1257ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 2867ms
Request Payload.Flags Request Uses V2 Query Param ✅ 2795ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 2865ms
Request Payload.Flags Request Omits Authorization Header ✅ 2867ms
Request Payload.Token In Flags Body Matches Init ✅ 2843ms
Request Payload.Groups Round Trip ✅ 2753ms
Request Payload.Groups Default To Empty Object ✅ 2725ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 2845ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 2769ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 2785ms
Request Lifecycle.No Flags Request On Init Alone ✅ 80ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 454ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 5479ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 2831ms
Retry Behavior.Retries Flags On 502 ✅ 2903ms
Retry Behavior.Retries Flags On 504 ✅ 2860ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 2999ms

@greptile-apps

greptile-apps Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Retrigger

[High risk] Removes public API surface for iOS SDK major version.

The PR appears safe to merge; no actionable regression was identified.

Reviews (1) · Last reviewed commit: "chore!: remove deprecated public APIs fo..."

turnipdabeets and others added 6 commits November 9, 2026 15:43
…ading

/flags still returns enabled: true for a flag with an empty variant, so
other PostHog SDKs can still report it as enabled after this upgrade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: let wrapper SDKs register capture options for every event

Adds registerOptionsForWrapper(_:) and unregisterOptionsForWrapper(_:)
behind @_spi(PostHogInternal), so posthog-flutter can offer global options
without adding public API to posthog-ios. Registered options are
persisted, cleared by reset(), saved into the crash context, and filled
under each event's own options before beforeSend runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: wait for each registered-options event on its own

* fix: read registered options under the lock that guards their writes

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix: omit null-valued event properties

Drop properties whose value is NSNull (or a nil boxed in Any) from analytics
and AI events when they are written to the queue and again when they are
sent, recursing into nested dictionaries and dictionaries inside arrays.
Null array elements keep their index and emptied dictionaries stay {}.
$set, $set_once and $group_set values are left as given so a caller can
still clear a person or group property.

Implements the sdk-specs capture requirement "Capture drops null-valued
object properties" and capture-ai's matching requirement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: drop nulls in dictionaries with non-string keys

A dictionary with number or boolean keys, such as one added in beforeSend,
skipped the null cleanup and was then stringified by sanitizeDictionary,
so its null members reached the queue as JSON null.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…cated

# Conflicts:
#	PostHog.podspec
#	PostHog/PostHogVersion.swift
#	PostHog/Surveys/PostHogSurveysDefaultDelegate.swift
#	PostHogTests/PostHogSurveyEventsTest.swift
@eli-r-ph

Copy link
Copy Markdown

#if os(iOS) || TESTING
// A survey still waiting out its popup delay belongs to the previous user.
let surveyIntegrations = setupLock.withLock { installedIntegrations.compactMap { $0 as? PostHogSurveyIntegration } }
surveyIntegrations.forEach { $0.cancelPendingSurvey() }

Merging main brought in the survey reset code from #1049 under #if os(iOS) || TESTING. On 4.0, PostHogSurveyIntegration is only built with the Surveys trait (#if os(iOS) && (!SWIFT_PACKAGE || Surveys)), so builds without that trait fail with cannot find type 'PostHogSurveyIntegration'. The guard needs to match.

turnipdabeets and others added 3 commits November 10, 2026 08:58
… fill $lib as a pair (#1050)

* fix: skip the person-properties hash for maybe-personless updates and fill $lib as a pair

The hash guard read only the process_person_profile option, but a registered
or legacy $process_person_profile false is hoisted into options later, at send.
Such a $set recorded a hash it couldn't apply, so the same update sent after
unregistering was dropped as a duplicate. Resolve the setting the way it's sent
(non-null option, then the legacy property) and record a hash only when it's
missing or a Boolean true.

Fill the SDK's $lib and $lib_version as a pair after the caller's properties,
per sdk-specs #146: another library's $lib never gets this SDK's version, and a
$lib_version without a $lib is replaced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: clear the person-properties hash when queueing a maybe-personful update

An update whose setting isn't a clear Boolean, such as the string "true",
skipped the hash but left the previous one in place. PostHog may still apply
it, so a later update back to the previous properties was dropped as a
duplicate. Clear the stored hash instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…cated

Brings in #1048 (drop flag loads in flight across reset()). PostHogRemoteConfig.swift
and its tests take the 4.0 resolution from #1044 (fix/v4-drop-flag-loads-across-reset),
which keeps 4.0's FeatureFlagsLoadResult callback and the removed didReceiveFeatureFlags.
PostHog.podspec stays deleted.
@turnipdabeets

Copy link
Copy Markdown
Contributor Author

Fixed in e27cbe4, the reset now uses the Surveys trait guard.

eli-r-ph and others added 7 commits November 10, 2026 17:10
…etting (#1053)

* docs: show how to keep identity calls under a registered personless setting

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: point registered personless users to identifiedOnly and note billing

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Anna Garcia <anna.g@posthog.com>
* feat: keep screenshotModeBackgroundCapture in 4.0

Reverts #937. GPU capture (#911) stays experimental and off by default,
so the main-thread drawHierarchy path remains the default in 4.0 and
background capture is the only off-main option. It unblocks the
main-thread hangs reported in #1035.

Drops the removed-API stub, the migration-guide row and the changeset
mention, and restores the public API snapshot line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(replay): skip hidden or transitioning windows before background capture

Background capture walked the hierarchy twice and rendered the window
before prepareScreenshotWireframe discarded the frame during a view
controller transition. Check visibility and transition state first, as
scheduleSettledCapture already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: order the restored screenshotModeBackgroundCapture snapshot line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(replay): release background-capture mask samples on the main thread

MaskedRegion retains its view, so the before sample could hold the last
reference to a view and release it on the replay queue, running UIKit
teardown off main. Drop it inside the second main.sync instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
RRWireframe only builds with SessionReplay in 4.0, so the no-traits test build failed on the tests main added in #1028.
4.0 renamed RRWireframe to RRScreenshotFrame, so the iOS test build failed on the tests from #1028. This replaces the trait gating from the previous two commits, which was the wrong fix: the type is not trait-gated.
Capture v1 moved to /docs/api/capture-v1 (posthog.com#21385). The masking table marks all three options as defaults, and the generated references copy is refreshed so it no longer says screenshotModeBackgroundCapture was removed.
turnipdabeets and others added 3 commits November 13, 2026 14:31
* fix: keep a registered personless setting off identity events

A registered `$process_person_profile: false`, or a `process_person_profile`
option a wrapper registers, no longer applies to `$identify`, `$create_alias`,
`$merge_dangerously`, `$groupidentify`, `$set` and `$set_once`. PostHog drops
those events with person processing off. A value passed on the call or set
in `beforeSend` still applies.

* docs: note the identity-event exception on register and capture

* docs: keep the $set and $set_once distinction in the migration guide

* test: cover $set_once, $merge_dangerously, a call property and beforeSend
…#1061)

* docs: sort the iOS 4.0.0 migration guide by when you make each change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: keep the 4.0.0 guide's table cells to one value

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: move the posthog.com iOS SDK pages into the reference

The posthog.com iOS SDK pages redirect to this reference once 4.0.0
ships, so their setup, how-to, and configuration sections now live in
docs/getting-started.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: title a single migration guide's page with its H1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 4f8c153)

* Revert "docs: move the posthog.com iOS SDK pages into the reference"

This reverts commit 0740925.

* docs: add the iOS facts from the retired posthog.com pages that no other page has

Drop the CocoaPods install line, since 4.x.x isn't published to
CocoaPods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: move the iOS one-API facts from getting-started into doc comments

The sampling note moves to setBeforeSend. The surveys swizzling note is
dropped, since 4.x.x surveys don't need swizzling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: preserve ios reference details

* docs: name the SDK in versions, cut self-hosted notes, and split dashes and semicolons

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: address review on the reordered iOS 4.0 migration guide

- Say 4.x.x sends analytics and AI events to capture v1, not every event
- Say follow ups can wait because 4.x.x still accepts the old names and options
- Add a checklist item for filters on a null `$feature_flag_response`
- Say what `$ai_*` events sent with `capture` still do, and what `captureAi` adds
- Say AI events queued by 4.x.x are sent again after a re-upgrade
- Reword the retry rules intro, since not all of them are about failures
- Regenerate references/posthog-ios-migration.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Anna Garcia <anna.g@posthog.com>
…cated

Brings in 3.95.0-3.96.2 and GPU screenshot capture (#911, #966).
Resolved for 4.0: screenshot capture always on (screenshotMode stays
removed, GPU capture docs no longer reference it), RRWireframe ->
RRScreenshotFrame, PostHog.podspec stays deleted, new GPU/replay
sources and tests gated on the SessionReplay trait, survey cancel
events gated on Surveys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
turnipdabeets and others added 4 commits November 13, 2026 15:53
The tests main added in #1059 expect NSNull, but 4.0 omits null
properties, and the feature-flag-called spec allows an omitted response
as the missing sentinel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	PostHog.podspec
#	PostHog/PostHogVersion.swift
…cated

Brings in #1054 (cleared labels and replaced images stay masked, and the
app-defined CALayer presentation guard from #1064).

- isLabelSensitive keeps 4.0's image-attachment check alongside #1054's
  cleared-text check.
- toWireframe stays removed (4.0 has no wireframe mode); main's
  hasSensitiveImageAttachment is dropped for 4.0's existing one.
- #1054's wireframe-only test is dropped, its fixture uses
  RRScreenshotFrame, and the new replay test files are gated on the
  SessionReplay trait like the other replay suites.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Next iOS major version plan $locale collected different from iOS

6 participants