Repository navigation
chore: 4.0 fix-it stubs for removed APIs and review nits - #977
turnipdabeets wants to merge 4 commits into
Conversation
Apps still using a renamed or removed 3.x API now get a compile error that names the replacement (an Xcode fix-it for plain renames) instead of "cannot find in scope". Swift-only; not exposed to Objective-C. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gate Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Queues read the connection without waiting for NWPathMonitor's first path and let that path's reachability notification set the real state, instead of waiting up to 100 ms during setup. An unknown connection keeps the existing behavior: .any doesn't pause, .wifi stays paused. Event context still waits, so setup events keep $network_wifi and $network_cellular. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Matches the gate #975 puts on PostHogConsoleLogLevel and PostHogConsoleLogEntry, so the stubs never reference a type the trait leaves out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
posthog-ios Compliance ReportDate: 2026-10-27T17:41:44.618246+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 2997ms |
| Format Validation.Event Has Uuid | ✅ | 2843ms |
| Format Validation.Event Has Lib Properties | ✅ | 2774ms |
| Format Validation.Distinct Id Is String | ✅ | 2706ms |
| Format Validation.Token Is Present | ✅ | 2874ms |
| Format Validation.Custom Properties Preserved | ✅ | 2896ms |
| Format Validation.Event Has Timestamp | ✅ | 2795ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 2865ms |
| Retry Behavior.Retries On 503 | ✅ | 11753ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 4841ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2452ms |
| Retry Behavior.Respects Retry After Header | ✅ | 5299ms |
| Retry Behavior.Implements Backoff | ✅ | 21810ms |
| Retry Behavior.Retries On 500 | ✅ | 9171ms |
| Retry Behavior.Retries On 502 | ✅ | 9286ms |
| Retry Behavior.Retries On 504 | ✅ | 5401ms |
| Retry Behavior.Max Retries Respected | ❌ | 21298ms |
| Deduplication.Generates Unique Uuids | ✅ | 3047ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 9153ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 16711ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 7890ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 2806ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 2743ms |
| Compression.Sends Gzip When Enabled | ✅ | 2812ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 2796ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 233ms |
| Batch Format.Multiple Events Batched Together | ✅ | 2915ms |
| Error Handling.Does Not Retry On 403 | ✅ | 4823ms |
| Error Handling.Does Not Retry On 413 | ✅ | 4725ms |
| Error Handling.Retries On 408 | ✅ | 8756ms |
Failures
retry_behavior.max_retries_respected
Expected 4 requests, got 6
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 2749ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 2867ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 2803ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 2822ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 2805ms |
| Request Payload.Groups Round Trip | ✅ | 2704ms |
| Request Payload.Groups Default To Empty Object | ✅ | 2771ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 2853ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 3369ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 2807ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 220ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 2914ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 5701ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 2811ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 2925ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 2759ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 3198ms |
|
@ioannisj do you think we really need this if we have a migration doc? It might be better to have compiler fail on the removed APIs so agents have to switch them. This still leaves a way to use deprecated APIs. I’m inclined to close this. |
There was a problem hiding this comment.
To be honest what I had in mind was deprecation messages in a few 3.x version before rolling out 4.0. I really like this approach though cause nothing old is really callable, and agents (or human) are presented with the fix right in the compile error, rather jumping to MIGRATION.md and guessing.
There are things we can't cover with stubs, so I think both could work well together. We can keep this around for a few 4.x minor versions before removing. One change I'd made, is define this in a dedicated extension file. Removing them would be just removing that one file from the project if we want to remove clutter down the road.
I think we are missing maybe screenshotMode and didReceiveFeatureFlags which PR body mentions. For screenshotMode the message can be similar to delete the assignment, replay always records masked screenshots for example
@ioannisj My thought was that this is a major release so it's ok. We can deprecate in 3.x but some people might not even see that between the update to 4.0 and as long as we have a good migration plan agents should be ok. Some of these things were only really ment to be internal anyway. What I don't like about this is it allows them to call these Apis but they are no ops which I think could confuse agents. |
💡 Motivation and Context
Addresses ioannisj's review on #873 (#873). We're not shipping a last 3.x release with deprecations, so 4.0 itself should point apps at the replacement when they hit a removed API. Pairs with #975 (see the trait gate note below).
1. Unavailable stubs for renamed and removed 3.x APIs
Code that still uses an old name now fails with an error that names the replacement, plus an Xcode fix-it for plain renames, instead of "cannot find in scope":
PostHogLogLevel,PostHogLogEntry(iOS)PostHogConsoleLogLevel/PostHogConsoleLogEntryPostHogConfig(apiKey:),PostHogConfig(apiKey:host:),PostHogConfig.apiKeyinit(projectToken:),init(projectToken:host:),projectTokenPostHogConfig.evaluationEnvironmentsevaluationContextsPostHogConfig.PostHogDataMode.cellular.any(a static property, not an enum case, soinit(rawValue: 1)still returnsniland switches stay exhaustive)PostHogSessionReplayConfig.debouncerDelay(iOS)throttleDelayPostHogSDK.getFeatureFlagPayload(_:)getFeatureFlagResult(key, sendFeatureFlagEvent: false)?.payloadPostHogConfig.propertiesSanitizer,PostHogPropertiesSanitizersetBeforeSend(_:)PostHogConfig.remoteConfig,PostHogSessionReplayConfig.maskPhotoLibraryImages,PostHogSessionReplayConfig.screenshotModeBackgroundCaptureThe
PostHogLogLevel/PostHogLogEntrystubs are behind#if !SWIFT_PACKAGE || SessionReplay, the gate #975 puts onPostHogConsoleLogLevel/PostHogConsoleLogEntry, so they never point at a type the trait leaves out. The replay config stubs are in an extension inPostHogSessionReplayConfig.swift, so #975's file-level gate covers them too. Either PR can merge first.The stubs aren't
@objc, so Objective-C callers see the old selectors go away like before; this only helps Swift. TheapiKey:host:stub has no defaulthost, soPostHogConfig(apiKey:)resolves to exactly one stub and nothing overlaps with theprojectToken:initializers.Not stubbed:
Gzip,toISO8601String,UIColor(hex:),dateToMillis,ReachabilityErrorand so on): apps aren't expected to use them.PostHogSessionManager,PostHogStorageManager,postHogSdkName, ...): a stub would clash with the@_spideclaration of the same name.reloadFeatureFlags(_:)callback change: Swift already offers a fix-it (insert_ in), and an old-signature overload would make{ }closures ambiguous.screenshotModeanddidReceiveFeatureFlags: other PRs handle these.Public API snapshot:
swift-symbolgraph-extractleaves out unconditionally unavailable declarations, soapi/posthog-ios.public-api.txtdoesn't change (I ran the snapshot update and none of the stubs appeared). The stubs aren't callable API, so that's accurate.2. Keep-in-sync comment
Added to the platform check in the stub
PostHogErrorTrackingAutoCaptureIntegration. It refers to "the gate at the top of the file" rather than to a line number.3. Reachability: queues no longer block setup on the first path
PostHogQueue.updatePauseStatenow reads a new non-blockingReachability.connectionIfKnowninstead of the waitingconnectiongetter.For an unknown connection I kept the rule #909 set (and
PostHogReplayBatchBoundaryTestspins):.anydoesn't pause,.wifistays paused. That's the same thing that happened before once the 100 ms wait ran out. If a.anysend goes out while the device is actually offline, it fails, the records stay on disk, and the send is retried. Then the first path'sonReachable/onUnreachablenotification sets the real state and flushes. That notification always arrives:startNotifier()reports a path that came in before the notifier started, andupdate(_:)reports one that comes in after. The read happens insidestateLock, so a first-path notification racing withstart()can't be overwritten by the stale unknown.I first tried pausing every queue while the connection is unknown. That broke the existing "Any data mode sends before the first path arrives" test, so I reverted to the existing contract rather than changing it here.
PostHogContext(for$network_wifi/$network_cellular) still uses the waiting getter. Setup capturesApplication Installed/Updated/Openedsynchronously, so without the wait those events would almost always lose the network properties. The wait ends as soon as the first path arrives (usually a few ms), with 100 ms as the cap. So when setup captures a lifecycle event, setup can still wait for the first path through the context, but the queues don't add any wait. The changeset line ("events captured in the first 100 ms after setup may leave out$network_wifi...") still holds, so I didn't edit it.💚 How did you test it?
projectTokeninits,.any/.wifi, an exhaustiveswitchonPostHogDataMode,PostHogDataMode(rawValue: 1),getFeatureFlagResult(_:sendFeatureFlagEvent:). I removed the file before committing.swiftc -typecheck -diagnostic-style llvmon a standalone copy of the declaration patterns (typealias, renamed init, renamed property, enum static property) shows the rename fix-it for each one.PostHogLogLevel,PostHogLogEntry, the replay config properties) compile in the iOSxcodebuildbuild. I didn't compile a usage check for them against iOS; they use the same declaration patterns as above.PostHogReachabilityNotifierTests.connectionIfKnownDoesNotWait. The existingPostHogReplayBatchBoundaryTests("Any data mode sends before the first path arrives", plus the Wi-Fi-mode test that stays paused until a path arrives) andPostHogLogsQueueTests.reachabilityPauseAndResumecover the queue behavior.make lint: passes.make test: fails only in tests outside this change. The app-groupPostHogStoragetests ("writes to disk in a project token folder under a group container directory", "excludes new and existing project folders from backup") fail the same way on an unmodified base checkout. A scattered set ofPostHogSDK/Surveys/AppLifeCycletests failed with 30 s request timeouts, and the set changed on every run. An unmodified base checkout running the same suites failed a different set of tests the same way, so I put this down to load from other test runs on the same machine. Before the last commit, which only touches iOS-only files that the macOS run compiles out, every reachability, queue and context suite passed.make testNoTraits: same two pre-existingPostHogStoragefailures, nothing else.make buildSdk: exits 127 becausexcprettyisn't installed. I ran each step of the recipe without it, and all pass: iOS,buildSdkSpm,buildSdkSpmNoTraits(macOS + iOS), macOS, Mac Catalyst, tvOS, watchOS, visionOS.make apiUpdate: samexcprettyproblem. I ran the script's steps without it: no snapshot change from this PR. The local toolchain also renders@Sendableinto some existing declarations, but that comes from the toolchain, not from this PR, so I didn't commit it.git diff --check: clean.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset fileNo new changeset: this goes into the 4.0 release entry in
.changeset/v4-major-release.md, and none of these changes alter what that entry describes.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Claude Code (Claude Opus 5.5) implemented this from a maintainer's brief that covered three of ioannisj's review threads on #873.
.anysends,.wifiwaits), and kept the wait in the event context, because dropping it there would strip network properties from setup lifecycle events.../../posthog-ios, soxcodebuildruns from an agent worktree need a temporaryposthog-iossymlink next to the worktree.xcprettyisn't installed on this machine, so I ran thebuildSdkandapiUpdatesteps without it.🤖 Generated with Claude Code