Skip to content

chore!: mark off-main public callbacks @Sendable for v4 - #914

Merged
turnipdabeets merged 4 commits into
chore/v4-reload-flags-resultfrom
chore/v4-sendable-callbacks
Oct 7, 2026
Merged

turnipdabeets merged 4 commits into
chore/v4-reload-flags-resultfrom
chore/v4-sendable-callbacks

Conversation

@turnipdabeets

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Part of the "Migrate to Swift 6 (and strong concurrency?)" item in #557. This PR only covers the part that needs a major version: the callbacks apps pass to the SDK. Compiling the SDK itself in Swift 6 language mode is internal work with no API impact, so it can follow in a 4.x minor.

Several public callbacks run off the main thread but aren't typed @Sendable. That causes two problems for apps:

  • Swift 6 apps crash. A reloadFeatureFlags { _ in ... } closure written in main-actor code (a @MainActor class or a SwiftUI view) is main-actor isolated, and the SDK calls it from its network completion queue. The Swift 6 runtime check traps (_swift_task_checkIsolatedSwift → SIGTRAP). The closure doesn't need to touch any state; { _ in print("x") } is enough. Swift 5 apps don't crash.
  • Silent data races. For closures passed variadically (setBeforeSend) or assigned to a property (getAnonymousId, logSanitizer, pushIdentityProvider), Swift doesn't insert that runtime check. A main-actor closure there compiles cleanly and then mutates main-actor state off the main thread.
API Runs on
reloadFeatureFlags(_:) callback network completion queue, or the caller's thread when the reload is skipped
setBeforeSend(_:) (events and logs) the thread that called capture / captureLog
getAnonymousId whichever thread generates the ID
logSanitizer the console pipe's background reader
pushIdentityProvider the SDK's push mint queue; its completion from any thread

Changes:

  • Mark those callbacks @Sendable. The compiler now catches main-actor access inside them, in Swift 5 and Swift 6 apps alike, and apps hop explicitly (Task { @MainActor in ... }). Objective-C is unaffected; the generated header doesn't change.
  • Make PostHogFeatureFlagsLoaded Sendable (@unchecked: all lets, and variants only holds Bool/String). Swift 6 apps can then pass it across tasks.
  • onFeatureFlags keeps its signature. It already delivers on the main thread, so main-actor closures pass the runtime check. Typing it @MainActor adds a Swift 6 error for apps that use plain classes and fixes nothing, so I left it out.
  • Document threading on getAnonymousId and logSanitizer, and tell callers of reloadFeatureFlags(_:) how to hop to the main actor. Fix PostHogExample, which set a @Published property from the callback on a background thread.

Who breaks:

App code inside these callbacks Swift 5 app Swift 6 app
Plain class capturing self nothing error
SwiftUI view setting @State warning warning
@MainActor class touching its own state error error

Every error lands on code that today either crashes (Swift 6) or races (Swift 5).

One upgrade hazard: a Swift 5 app that passes a pre-typed array such as let hooks: [(PostHogEvent) -> PostHogEvent?] to setBeforeSend(_:) gets only a warning, then aborts at runtime ("Could not cast value of type '(…) -> …' to '@sendable (…) -> …'"). Swift can't convert arrays of function types that differ only in @Sendable. Swift 6 apps get a compile error instead. Array literals, single closures, variadic calls and arrays built with map are fine. The SDK hit the same thing internally, which is why BeforeSendChain now stores @Sendable blocks. The changeset tells apps to type those arrays as [BeforeSendBlock] / [PostHogBeforeSendLogBlock].

Open question for reviewers: we could prevent that crash instead of documenting it. A Swift-only @available(swift, obsoleted: 6.0) @_disfavoredOverload setBeforeSend(_: [(PostHogEvent) -> PostHogEvent?]) on each config would take typed arrays in Swift 5 and box them, without changing overload resolution for literals or [BeforeSendBlock]. I left it out because it adds public API using an underscored attribute for a narrow pattern.

The Swift-only setBeforeSend(_:) overloads get new mangled symbol names, so prebuilt binaries need rebuilding. That's expected for a major version.

Spec: reload-feature-flags lists the iOS signature as reloadFeatureFlags(_ callback: @escaping () -> Void). With #913 and this PR it becomes @escaping @Sendable (PostHogFeatureFlagsLoaded) -> Void. Neither it nor on-feature-flags says anything about threads, so there's no behavior divergence.

Stacked on #913, which passes a PostHogFeatureFlagsLoaded to the reload callback. This PR adds @Sendable to that new signature, so the API breaks only once. #913 sits on #873, whose tools bump to 6.2 the per-target Swift 6 test setting needs. #912 renames PostHogLogEntry to PostHogConsoleLogEntry, so the logSanitizer line will conflict trivially.

💚 How did you test it?

  • New PostHogSwift6Tests target compiled in Swift 6 language mode. It calls the API from main-actor code the way an app does:
    • reloadFeatureFlags with a closure written in main-actor code, which passes its result into a main-actor Task. Without @Sendable the test process traps (signal 5); with this PR it passes.
    • onFeatureFlags delivers on main and its payload can be sent to another task. Without the Sendable conformance the target doesn't compile.
  • No runtime test covers the silent-race callbacks: nothing traps at runtime, and Swift Testing can't assert that code fails to compile. I checked them with throwaway Swift 6 and Swift 5 app packages (the table above).
  • make test: XCTest 169/169 passes. Swift Testing has 13 issues, all in PostHogStorageTest's app-group container tests; the base branch fails the same way on this machine, and nothing else fails.
  • make buildSdk: all 6 platforms build, with no concurrency warnings.
  • make buildExamplesPlatforms: all 8 example builds succeed.
  • iOS simulator: PostHogConsoleLogInterceptorTest and PostHogSessionReplayPluginTests pass (logSanitizer only runs on iOS).
  • make lint: no serious violations.
  • Regenerated api/posthog-ios.public-api.txt with make apiUpdate (xcpretty isn't installed here, so I ran it with a pass-through shim).
  • The change adds 29 compiler warnings to existing Swift 5 mode tests, all closures mutating captured vars inside these callbacks. Tests still pass. They'll be fixed with the SDK's Swift 6 language-mode migration, or I can do it here if reviewers prefer.
  • Reviewed by two separate review agents (correctness and compatibility). Their verified findings are applied: threading docs, a fuller changeset, and a test assertion that doesn't depend on the /flags request failing.
  • Not run: the full iOS simulator suite. The new Swift 6 target is SPM-only, so CI runs it in the macOS make test job, not the simulator job.

📝 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

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

  • Claude Code (Opus 5.5).
  • Considered and dropped: typing onFeatureFlags as @MainActor, and switching the SDK to Swift 6 language mode in this PR. A first build in Swift 6 mode shows at least 35 errors, mostly mutable globals, and that's internal work.
  • PostHogSDK: @unchecked Sendable (so Swift 6 apps can pass SDK instances into tasks and actors) is additive and goes in a separate PR.

🤖 Generated with Claude Code

@turnipdabeets turnipdabeets self-assigned this Oct 6, 2026
@turnipdabeets
turnipdabeets marked this pull request as ready for review October 6, 2026 14:59
@turnipdabeets
turnipdabeets requested a review from a team as a code owner October 6, 2026 14:59
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

posthog-ios Compliance Report

Date: 2026-10-07 02:18:33 UTC
Duration: 215454ms

⚠️ Some Tests Failed

44/45 tests passed, 1 failed


Capture Tests

⚠️ 28/29 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 2895ms
Format Validation.Event Has Uuid ✅ 2678ms
Format Validation.Event Has Lib Properties ✅ 325ms
Format Validation.Distinct Id Is String ✅ 2819ms
Format Validation.Token Is Present ✅ 573ms
Format Validation.Custom Properties Preserved ✅ 2949ms
Format Validation.Event Has Timestamp ✅ 2834ms
Retry Behavior.Retries On 503 ✅ 11799ms
Retry Behavior.Does Not Retry On 400 ✅ 4734ms
Retry Behavior.Does Not Retry On 401 ✅ 4760ms
Retry Behavior.Respects Retry After Header ✅ 7757ms
Retry Behavior.Implements Backoff ✅ 21802ms
Retry Behavior.Retries On 500 ✅ 5279ms
Retry Behavior.Retries On 502 ✅ 5443ms
Retry Behavior.Retries On 504 ✅ 5457ms
Retry Behavior.Max Retries Respected ❌ 21845ms
Deduplication.Generates Unique Uuids ✅ 3152ms
Deduplication.Preserves Uuid On Retry ✅ 9322ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10431ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 7992ms
Deduplication.No Duplicate Events In Batch ✅ 2894ms
Deduplication.Different Events Have Different Uuids ✅ 2821ms
Compression.Sends Gzip When Enabled ✅ 2781ms
Batch Format.Uses Proper Batch Structure ✅ 2701ms
Batch Format.Flush With No Events Sends Nothing ✅ 312ms
Batch Format.Multiple Events Batched Together ✅ 2985ms
Error Handling.Does Not Retry On 403 ✅ 4810ms
Error Handling.Does Not Retry On 413 ✅ 4809ms
Error Handling.Retries On 408 ✅ 9325ms

Failures

retry_behavior.max_retries_respected

Expected 4 requests, got 6

Feature_Flags Tests

✅ 16/16 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 2888ms
Request Payload.Flags Request Uses V2 Query Param ✅ 3075ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 2934ms
Request Payload.Flags Request Omits Authorization Header ✅ 2795ms
Request Payload.Token In Flags Body Matches Init ✅ 2736ms
Request Payload.Groups Round Trip ✅ 2854ms
Request Payload.Groups Default To Empty Object ✅ 2788ms
Request Payload.Person Properties Distinct Id Auto Populated When Caller Omits It ✅ 2808ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 2842ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 2830ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 2749ms
Request Lifecycle.No Flags Request On Init Alone ✅ 57ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 2816ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 5504ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 2761ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 3068ms

@dustinbyrne dustinbyrne 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.

Thanks—the callback annotations and major-version migration notes match the execution paths. One important issue remains in the loaded-flags payload: accepted mutable Foundation bootstrap variants can survive the filter, undermining its new unchecked Sendable guarantee. Could we store the bridged Bool/String values and add the snapshot regression described inline?

Agent-led review, human-reviewed before posting. Validation was static inspection plus existing CI evidence.

@dustinbyrne
dustinbyrne requested a review from a team October 6, 2026 18:26
turnipdabeets and others added 4 commits October 6, 2026 21:45
The reloadFeatureFlags callback, beforeSend blocks, getAnonymousId,
pushIdentityProvider and logSanitizer run off the main thread but weren't
@sendable. In Swift 6 apps a reloadFeatureFlags closure written in
main-actor code traps at runtime; the other callbacks race silently.
Also make PostHogFeatureFlagsLoaded Sendable and add a Swift 6
language-mode test target that calls the API as an app would.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Xcode 26.3's symbol graph leaves @sendable out of declaration
fragments, which 26.4 prints; the mangled names still differ.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The init kept the original variant values after filtering, so a mutable
NSString passed via bootstrap stayed mutable inside a Sendable payload.
Store the `as? Bool`/`as? String` results instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Name the config properties unambiguously and warn that array
conversions crash in both directions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@turnipdabeets
turnipdabeets force-pushed the chore/v4-reload-flags-result branch from ba7eefe to 59786d9 Compare October 7, 2026 02:05
@turnipdabeets
turnipdabeets force-pushed the chore/v4-sendable-callbacks branch from f83d9d2 to be9478f Compare October 7, 2026 02:05
@turnipdabeets
turnipdabeets merged commit 609cfe2 into chore/v4-reload-flags-result Oct 7, 2026
43 checks passed
@turnipdabeets
turnipdabeets deleted the chore/v4-sendable-callbacks branch October 7, 2026 18:20
turnipdabeets added a commit that referenced this pull request Oct 7, 2026
* chore!: pass the reload result to reloadFeatureFlags' callback

reloadFeatureFlags(_:) now hands its callback a PostHogFeatureFlagsLoaded,
the same type onFeatureFlags receives, so callers can tell a failed reload
from a successful one. errorsLoading is true when the request failed or the
SDK isn't set up; flags and variants then hold the last known values.

Part of #557.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: snapshot a failed reload's last known flags once for all callbacks

Coalesced reload callbacks each read the live cache on failure, so a
reset() in one callback emptied the last known flags reported to the
next. The load now captures its result once, before fan-out.

Also updates the setPersonPropertiesForFlags example and note for the
new callback argument.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test: gate the first failed reload so the reset regression always coalesces

The coalesced-failure + reset() test assumed the first reload was still
in flight when the next two registered. Hold its response until both
callbacks are pending, and check both shared one request.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore!: mark off-main public callbacks @sendable for v4 (#914)

* chore!: mark off-main public callbacks @sendable for v4

The reloadFeatureFlags callback, beforeSend blocks, getAnonymousId,
pushIdentityProvider and logSanitizer run off the main thread but weren't
@sendable. In Swift 6 apps a reloadFeatureFlags closure written in
main-actor code traps at runtime; the other callbacks race silently.
Also make PostHogFeatureFlagsLoaded Sendable and add a Swift 6
language-mode test target that calls the API as an app would.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: match the public API snapshot to CI's Xcode 26.3 output

Xcode 26.3's symbol graph leaves @sendable out of declaration
fragments, which 26.4 prints; the mangled names still differ.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: store bridged flag values in PostHogFeatureFlagsLoaded

The init kept the original variant values after filtering, so a mutable
NSString passed via bootstrap stayed mutable inside a Sendable payload.
Store the `as? Bool`/`as? String` results instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: clarify the Sendable callbacks changeset

Name the config properties unambiguously and warn that array
conversions crash in both directions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
turnipdabeets added a commit that referenced this pull request Oct 9, 2026
* chore!: pass the reload result to reloadFeatureFlags' callback

reloadFeatureFlags(_:) now hands its callback a PostHogFeatureFlagsLoaded,
the same type onFeatureFlags receives, so callers can tell a failed reload
from a successful one. errorsLoading is true when the request failed or the
SDK isn't set up; flags and variants then hold the last known values.

Part of #557.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: snapshot a failed reload's last known flags once for all callbacks

Coalesced reload callbacks each read the live cache on failure, so a
reset() in one callback emptied the last known flags reported to the
next. The load now captures its result once, before fan-out.

Also updates the setPersonPropertiesForFlags example and note for the
new callback argument.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test: gate the first failed reload so the reset regression always coalesces

The coalesced-failure + reset() test assumed the first reload was still
in flight when the next two registered. Hold its response until both
callbacks are pending, and check both shared one request.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore!: mark off-main public callbacks @sendable for v4 (#914)

* chore!: mark off-main public callbacks @sendable for v4

The reloadFeatureFlags callback, beforeSend blocks, getAnonymousId,
pushIdentityProvider and logSanitizer run off the main thread but weren't
@sendable. In Swift 6 apps a reloadFeatureFlags closure written in
main-actor code traps at runtime; the other callbacks race silently.
Also make PostHogFeatureFlagsLoaded Sendable and add a Swift 6
language-mode test target that calls the API as an app would.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: match the public API snapshot to CI's Xcode 26.3 output

Xcode 26.3's symbol graph leaves @sendable out of declaration
fragments, which 26.4 prints; the mangled names still differ.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: store bridged flag values in PostHogFeatureFlagsLoaded

The init kept the original variant values after filtering, so a mutable
NSString passed via bootstrap stayed mutable inside a Sendable payload.
Store the `as? Bool`/`as? String` results instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: clarify the Sendable callbacks changeset

Name the config properties unambiguously and warn that array
conversions crash in both directions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
turnipdabeets added a commit that referenced this pull request Oct 9, 2026
The @sendable changes from #914 didn't update the snapshot.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
turnipdabeets added a commit that referenced this pull request Oct 20, 2026
* chore!: pass the reload result to reloadFeatureFlags' callback

reloadFeatureFlags(_:) now hands its callback a PostHogFeatureFlagsLoaded,
the same type onFeatureFlags receives, so callers can tell a failed reload
from a successful one. errorsLoading is true when the request failed or the
SDK isn't set up; flags and variants then hold the last known values.

Part of #557.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: snapshot a failed reload's last known flags once for all callbacks

Coalesced reload callbacks each read the live cache on failure, so a
reset() in one callback emptied the last known flags reported to the
next. The load now captures its result once, before fan-out.

Also updates the setPersonPropertiesForFlags example and note for the
new callback argument.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test: gate the first failed reload so the reset regression always coalesces

The coalesced-failure + reset() test assumed the first reload was still
in flight when the next two registered. Hold its response until both
callbacks are pending, and check both shared one request.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore!: mark off-main public callbacks @sendable for v4 (#914)

* chore!: mark off-main public callbacks @sendable for v4

The reloadFeatureFlags callback, beforeSend blocks, getAnonymousId,
pushIdentityProvider and logSanitizer run off the main thread but weren't
@sendable. In Swift 6 apps a reloadFeatureFlags closure written in
main-actor code traps at runtime; the other callbacks race silently.
Also make PostHogFeatureFlagsLoaded Sendable and add a Swift 6
language-mode test target that calls the API as an app would.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: match the public API snapshot to CI's Xcode 26.3 output

Xcode 26.3's symbol graph leaves @sendable out of declaration
fragments, which 26.4 prints; the mangled names still differ.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: store bridged flag values in PostHogFeatureFlagsLoaded

The init kept the original variant values after filtering, so a mutable
NSString passed via bootstrap stayed mutable inside a Sendable payload.
Store the `as? Bool`/`as? String` results instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: clarify the Sendable callbacks changeset

Name the config properties unambiguously and warn that array
conversions crash in both directions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
turnipdabeets added a commit that referenced this pull request Oct 23, 2026
* chore!: pass the reload result to reloadFeatureFlags' callback

reloadFeatureFlags(_:) now hands its callback a PostHogFeatureFlagsLoaded,
the same type onFeatureFlags receives, so callers can tell a failed reload
from a successful one. errorsLoading is true when the request failed or the
SDK isn't set up; flags and variants then hold the last known values.

Part of #557.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: snapshot a failed reload's last known flags once for all callbacks

Coalesced reload callbacks each read the live cache on failure, so a
reset() in one callback emptied the last known flags reported to the
next. The load now captures its result once, before fan-out.

Also updates the setPersonPropertiesForFlags example and note for the
new callback argument.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test: gate the first failed reload so the reset regression always coalesces

The coalesced-failure + reset() test assumed the first reload was still
in flight when the next two registered. Hold its response until both
callbacks are pending, and check both shared one request.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore!: mark off-main public callbacks @sendable for v4 (#914)

* chore!: mark off-main public callbacks @sendable for v4

The reloadFeatureFlags callback, beforeSend blocks, getAnonymousId,
pushIdentityProvider and logSanitizer run off the main thread but weren't
@sendable. In Swift 6 apps a reloadFeatureFlags closure written in
main-actor code traps at runtime; the other callbacks race silently.
Also make PostHogFeatureFlagsLoaded Sendable and add a Swift 6
language-mode test target that calls the API as an app would.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: match the public API snapshot to CI's Xcode 26.3 output

Xcode 26.3's symbol graph leaves @sendable out of declaration
fragments, which 26.4 prints; the mangled names still differ.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: store bridged flag values in PostHogFeatureFlagsLoaded

The init kept the original variant values after filtering, so a mutable
NSString passed via bootstrap stayed mutable inside a Sendable payload.
Store the `as? Bool`/`as? String` results instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: clarify the Sendable callbacks changeset

Name the config properties unambiguously and warn that array
conversions crash in both directions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
turnipdabeets added a commit that referenced this pull request Oct 26, 2026
* chore!: pass the reload result to reloadFeatureFlags' callback

reloadFeatureFlags(_:) now hands its callback a PostHogFeatureFlagsLoaded,
the same type onFeatureFlags receives, so callers can tell a failed reload
from a successful one. errorsLoading is true when the request failed or the
SDK isn't set up; flags and variants then hold the last known values.

Part of #557.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: snapshot a failed reload's last known flags once for all callbacks

Coalesced reload callbacks each read the live cache on failure, so a
reset() in one callback emptied the last known flags reported to the
next. The load now captures its result once, before fan-out.

Also updates the setPersonPropertiesForFlags example and note for the
new callback argument.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test: gate the first failed reload so the reset regression always coalesces

The coalesced-failure + reset() test assumed the first reload was still
in flight when the next two registered. Hold its response until both
callbacks are pending, and check both shared one request.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore!: mark off-main public callbacks @sendable for v4 (#914)

* chore!: mark off-main public callbacks @sendable for v4

The reloadFeatureFlags callback, beforeSend blocks, getAnonymousId,
pushIdentityProvider and logSanitizer run off the main thread but weren't
@sendable. In Swift 6 apps a reloadFeatureFlags closure written in
main-actor code traps at runtime; the other callbacks race silently.
Also make PostHogFeatureFlagsLoaded Sendable and add a Swift 6
language-mode test target that calls the API as an app would.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: match the public API snapshot to CI's Xcode 26.3 output

Xcode 26.3's symbol graph leaves @sendable out of declaration
fragments, which 26.4 prints; the mangled names still differ.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: store bridged flag values in PostHogFeatureFlagsLoaded

The init kept the original variant values after filtering, so a mutable
NSString passed via bootstrap stayed mutable inside a Sendable payload.
Store the `as? Bool`/`as? String` results instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: clarify the Sendable callbacks changeset

Name the config properties unambiguously and warn that array
conversions crash in both directions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

2 participants