Repository navigation
feat!: make beforeSend callbacks synchronous - #686
turnipdabeets wants to merge 7 commits into
Conversation
Event and log beforeSend callbacks no longer return FutureOr, matching the native SDKs and the before-send-hook spec. The event is handed to the SDK in the same call, so calls made right after an un-awaited capture can no longer reach the SDK first, on every platform including Windows and Linux. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
posthog-flutter Compliance ReportDate: 2026-10-31 02:19:45 UTC ✅ All Tests Passed!45/45 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 16/16 tests passed View Details
|
marandaneto
left a comment
There was a problem hiding this comment.
The code looks good; no qualifying findings. Verdict: correct.
|
approved to unblock, and i like the idea of being sync, but message channels are async, so not sure how this is gonna work |
| "posthog_flutter": major | ||
| --- | ||
|
|
||
| - **Breaking:** `beforeSend` and `logsConfig.beforeSend` callbacks are now synchronous (`PostHogEvent? Function(PostHogEvent)` and `PostHogLogRecord? Function(PostHogLogRecord)`), as in the native SDKs. An event is now handed to the SDK in the same call, so a `reset()`, `identify()` or `optOut()` made right after an un-awaited `capture()`, `screen()` or `captureException()` no longer applies to it first, and Windows and Linux no longer drop it. Callbacks that used `await` must load what they need ahead of time |
There was a problem hiding this comment.
With #682 on iOS, macOS and Android, a capture() followed straight away by optOut() (or close()/setup()) drops that event, so this holds only for Windows and Linux. Could the changeset say so?
There was a problem hiding this comment.
Fixed differently in 41ea6b0: the event isn't dropped, because iOS, macOS and Android keep CallOrder and those calls wait until it's sent (test on #682, 0fc1715). So the changeset's claim now holds on every platform, and I only removed its "same call" wording, which isn't true with #682's two native calls.
Removes the CallOrder queue #672 brought in from main, since synchronous beforeSend makes it redundant.
There was a problem hiding this comment.
PostHog Review · Standard · reviewed c3e4a07: 1 Should fix.
See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.
With #682 and beforeSend set, an event takes two native calls. Without the queue, an optOut(), close() or setup() made right after capture() reaches native between them and the event is lost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without CallOrder the await on the flag lookup let a reset() made right after an un-awaited captureFeatureView or captureFeatureInteraction overtake it, so the event went out as the new anonymous user with the previous user's flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
💡 Motivation and Context
Flutter is the only PostHog SDK whose
beforeSendcan be async. posthog-js, iOS, Android, React Native and KMP all run it synchronously after building the event, and thebefore-send-hookspec in sdk-specs requires that ("Hooks run synchronously with event preparation", "does not ... reorder events").Because Flutter awaited the callbacks before handing the event to posthog-js or native, a call made right after an un-awaited
capture(),screen()orcaptureException()could reach the SDK first. WithPosthog().capture('logged_out'); Posthog().reset();:logged_outas the next anonymous user;#672 fixes this for 5.x with a call queue (
CallOrder). Making the callbacks synchronous fixes it at the source, so the queue can go from web and Windows/Linux in 6.0. iOS, macOS and Android keep it, because with #682 andbeforeSendset an event takes two native calls (buildEvent, thensendBuiltEvent). Without the queue, anoptOut(),close()orsetup()right aftercapture()would reach native between them and the event would be lost.Changes
BeforeSendCallbackis nowPostHogEvent? Function(PostHogEvent), andBeforeSendLogCallbackisPostHogLogRecord? Function(PostHogLogRecord). Anasynccallback is now a compile error, not a silent behavior change.applyBeforeSendis synchronous. Web, the method-channel platforms (iOS, Android, macOS), Windows/Linux and logs all hand the event over in the same call ascapture().setup()after the callbacks is removed, since there's no longer anything to wait across.beforeSenddoc.Migration: a callback that used
await(to fetch remote config, for example) has to load that data ahead of time and read it synchronously, as on iOS, Android and JS. I'll add this to the upgrade guide (PostHog/posthog.com#20924).💚 How did you test it?
capture,screenandcaptureExceptionfollowed byreset()reach the SDK first, with and withoutbeforeSend, on IO, web and desktop (desktop also coversidentify()). All fail on chore!: Flutter Major Release v6 #637's code and pass here.close()/setup(), a project or host change, opt-out, identify or reset. Those situations can no longer happen. The two desktop "does not admit an event made while opted out" tests now use synchronous hooks.FLUTTER_BUILD_NAME): 71 pass.flutter analyzeanddart formatare clean.api/posthog_flutter.api.jsonis regenerated.Merge notes
chore/v6-posthog-ios-4.posthog_flutter_io.dart, withapplyBeforeSendsynchronous. feat!: run beforeSend on the native-built event on iOS, macOS and Android #682's testwaits for an event between its build and sendfails if the queue is removed.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Claude Code opened this as the v6 follow-up to #672. Before making the change, it checked that posthog-js, iOS, Android, React Native and KMP all run
beforeSendsynchronously after building the event. On Windows and Linux,_clientForContinuationstill drops an event if a callback itself callsreset(),optOut()orsetup(). That's the only remaining trigger. I left it in place.🤖 Generated with Claude Code