Repository navigation
fix(desktop): skip phx_ keys, keep response bodies out of logs, count duplicate copies - #681
Open
turnipdabeets wants to merge 48 commits into
Open
turnipdabeets wants to merge 48 commits into
turnipdabeets wants to merge 48 commits into
Conversation
- Require posthog-ios 4.x. - Adapt to the reloadFeatureFlags(_:) callback that now passes a PostHogFeatureFlagsLoaded (PostHog/posthog-ios#913). - Note the payload decoding changes from PostHog/posthog-ios#903. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PostHog/posthog-ios#905 removes the legacy version/build properties from lifecycle events in 4.0, so Flutter iOS and macOS apps stop sending them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…listener posthog-ios 4.0 removes the didReceiveFeatureFlags notification.
Passes options and captureAi through to posthog-ios and posthog-android 4.0; Windows, Linux and web send known options as their legacy properties.
Add captureAiCompression, captureAiMaxQueueSize, captureAiTimeout and captureAiMaxEventBytes. Set values are sent to native setup and applied to the posthog-ios and posthog-android config; unset values are omitted so the native defaults apply. They have no effect on Windows, Linux and web. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
null means the event wasn't admitted: dropped by beforeSend, oversized on iOS and macOS, SDK disabled or opted out, or an unsupported platform. On Windows, Linux and web captureAi is a no-op that sends nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
posthog-android now checks the size before queuing (f46a9711). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 404 usually means a misconfigured proxy and a 429 an overloaded Envoy; both are worth retrying with the same backoff, Retry-After handling and retry budget. Redirects stay terminal.
…e 404 After a batch fails its retries, later flushes make one attempt each and pause 6s doubling to 30s until a batch is delivered. Before, every flush, including the one each capture starts at flushAt, sent 4 requests, so a missing capture route kept resending the batch. A 404 prints one warning per client even without debug, naming the URL and the path a proxy or self-hosted instance must serve.
A host with proxy credentials in its userinfo would print them to app logs.
… fix/v6-desktop-retry-404-429 # Conflicts: # .changeset/v6-desktop-capture-v1.md
#664) * chore!: pass the loaded flags to onFeatureFlags and reloadFeatureFlags onFeatureFlags and reloadFeatureFlags now carry a PostHogFeatureFlagsLoaded with the enabled flags, their values and whether loading failed, matching posthog-ios 4.0 and posthog-android 4.0. On web, reloadFeatureFlags completes right away with the flags loaded so far, because posthog-js doesn't report when a reload finishes. * fix: complete the web reloadFeatureFlags with the reloaded flags Wait for the next posthog-js onFeatureFlags call instead of returning the flags already loaded, so a failed reload reports errorsLoading. Complete right away when flags are disabled, and after twice the flags request timeout when posthog-js skips the request without calling back. * chore: use PostHogFeatureFlagsCallback from posthog-android 4.0 * chore: fix ktlint function-signature * fix: await posthog-js reloadFeatureFlagsAsync on web The next onFeatureFlags callback can carry a response from a request already in flight, so reloadFeatureFlags() could complete with flags from before the reload. posthog-js 1.438.2+ settles reloadFeatureFlagsAsync() after this reload's own request; use it when present and keep the callback path for older posthog-js. * docs: pass the loaded flags in the setup example * docs: drop best-effort web note from the person and group property methods * fix(web): wait for posthog-js flags retries before reporting a reload failure posthog-js retries a timed-out /flags request once by default, 500 ms later, inside the same reload. The older-JS deadline of twice the request timeout reported cached flags with errorsLoading set before a successful retry landed. * docs: note where onFeatureFlags and errorsLoading differ on Android and web * docs: describe the older posthog-js reload timeout as the full retry budget Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… fix/v6-desktop-retry-404-429 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to feat/v6-desktop-capture-ai
On Windows and Linux, the options of capture, screen, captureException, identify, setPersonProperties, alias and group now go into the event's capture v1 `options` as they are, unknown keys included, instead of being mapped to legacy `$` properties. An option still wins over its legacy property, which is moved into `options` only when the option is unset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows and Linux, an event's properties are now the ones the SDK sets, then the registered ones, then the event's own, each winning over the ones before, and the Dart beforeSend callbacks run after that merge. They see and can change every property and option the SDK adds, and the event is queued as they leave it. A caller or registered value changes that event only. Person processing is no longer a `$process_person_profile` property: the event carries `process_person_profile: false` in its options only while person processing is off and neither an option nor a property sets it, and omits it otherwise. The event's timestamp is taken before the callbacks run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g-android A transient failure or a 2xx whose per-event results ask to retry no longer resends within the flush (3 tries, 3 s apart). Each one pauses the queue for 1 s doubling to 30 s, with ±25% jitter from an injectable source. A Retry-After is a floor applied after the jitter, capped at 30 s, instead of replacing the backoff, so `Retry-After: 1` no longer resends every 3 s. A delivered or dropped batch resets the backoff. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uration The 10 s whole-call timeout could fail a large batch on a slow uplink on every attempt. A request now fails only after 10 s without progress: connecting (HttpClient.connectionTimeout), the socket taking a 64 KiB body chunk, the response headers, or a response body chunk. This is the closest dart:io gets to the per-read/per-write idle timeouts of OkHttp and URLSession: like them, it can't see the bytes still in the kernel send buffer, so the wait for headers starts once the last chunk is handed over. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 413 halved only the batch size and resent at once in the same flush. Like posthog-ios and posthog-android, it now also halves flushAt, kept within the batch size, and the smaller batches go out with the next flush (the next capture over flushAt, or the periodic flush). No backoff is added: posthog-ios ends the flush without one; posthog-android pauses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows and Linux, captureAi now builds the event like capture, runs the Dart beforeSend callbacks, and queues it on disk apart from other events, in posthog_ai_queue/. It is sent to /i/v1/ai/events with the same wire format, batching, retry rules and backoff as analytics events, and returns the event's uuid, or null when the event isn't queued: dropped by beforeSend, opted out, or over the size limit. - A serialized event over captureAiMaxEventBytes, capped at and by default 8 MiB plus 64 KiB, measured before compression, is dropped with a log line naming only the event and its size. - The queue keeps captureAiMaxQueueSize events (100 by default) and at most 50 MiB, dropping the oldest first, and flushes as soon as 5 MiB is queued. - Requests use captureAiCompression (gzip by default) and are bounded by captureAiTimeout (60 seconds by default). - Each endpoint keeps its own retry identity, timers, backoff and 413 batch size, so a failing AI request doesn't hold back other events. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esets Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Like posthog-ios and posthog-android, a full capture v1 request's worth of queued bytes (captureV1MaxBatchBytes) now flushes as flushAt does, so a few large events go out before the queue starts dropping them. The file queue keeps each event's serialized size: measured when it is added, and read from the file sizes when a later process loads the queue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #678's backoff, idle-progress timeout, 413 handling and 5 MiB queued-bytes flush trigger, applied per capture lane so the analytics and AI endpoints each keep their own backoff, flushAt and batch size. - FileEventQueue: one size tracker, a running total (byteCount) kept on add, remove and load, used by both the 5 MiB flush trigger and the AI 50 MiB cap. add() still takes pre-encoded json/bytes to skip a second encode. - _CaptureLane gains flushAt; failedFlushes now drives captureRetryDelay per lane, and a 413 halves the lane's batch size and flushAt and ends its flush. - captureAiTimeout is the AI lane's idle-progress timeout, like analytics' 10 s, instead of a whole-request bound. - Test: the AI and analytics endpoints back off independently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
$set/$set_once were stored for flag evaluation while the event was prepared, before the beforeSend callbacks ran, so a redacted value or a dropped event still reached the next /flags request. They are now taken from the event as it is queued. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The payload builder merged the common properties ($feature/<key>, $active_feature_flags, $recording_status, $lib) under every event again, so a property the callbacks removed came back. Every core path already builds them into the event's properties, so the payload adds nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The flush peeked up to maxBatchSize events, decoding each one, before the 5 MiB batch cap picked the few it sends. With 8 MiB AI events queued that read far more than one request carries, on the UI isolate. The queue now applies the cap from its tracked per-event sizes before it reads an event, always keeping the first, so only the batch is decoded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A flush that kept finding queued events held every delivered payload until it ended. Emit flush per batch so multi-megabyte AI events are released as soon as they are sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A body that trickles in more often than the 10 s idle timeout reset it forever, holding the flush. The body now also fails after 30 s in total. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to consolidate-679 # Conflicts: # posthog_flutter/lib/src/core/posthog_core_stateless.dart
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… duplicate copies - Posthog.setup skips a projectToken that is a personal API key (phx_), like the native SDKs, so the key never goes on the wire (INV-32). - PostHogFetchHttpError.toString leaves out the response body, which can echo request values (FC-5/FQ-18); the flush error log names the lane. - captureV1Results reads duplicates_dropped and the per-batch log reports the dropped copies (FQ-19).
Contributor
posthog-flutter Compliance ReportDate: 2026-10-29T20:32:50.574563+00:00 ✅ All Tests Passed!47/47 tests passed Capture Tests✅ 30/30 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
|
marandaneto
reviewed
Oct 29, 2026
marandaneto
left a comment
Member
There was a problem hiding this comment.
Automated advisory code review.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ci: update SDK compliance harness to 1.25.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(compliance): bump the test harness to 1.26.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Picks up #664's posthog-android 4.0 feature-flags callback; the desktop test's onFeatureFlags now takes the loaded flags.
…dpoint and reasons - A 2xx whose body has no readable per-event results fails the batch for good: it's dropped, not resent, and reported through the error event, matching posthog-ios and posthog-android. - One aggregate line per batch that lost events, naming the endpoint, the count and capture's `details` tags (never uuids or content). A terminal status logs how many events it lost; a multi-event 413 still halves. - A `limited` result counts as delivered, like any unknown result. - The non-string session/window id warning names the JSON type `boolean`. - Test fakes answer capture requests with per-event results, as capture v1 does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from
feat/v6-desktop-capture-ai
to
feat/v6-desktop-capture-v1
October 29, 2026 21:05
This branch has not been deployed
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.
💡 Motivation and Context
Small follow-ups on the Windows/Linux capture v1 client (#679), to match the native iOS and Android SDKs.
phx_): capture always rejects them, and iOS/Android already disable the SDK for one. Desktop sent the key on every request.Posthog.setupnow skips setup with one log line, the same way it handles a blank token.PostHogFetchHttpError.toString()included the server's response, which can echo values from the request. That string is printed by the flush error log. TheresponseBodyfield is still there for code that needs it.duplicates_dropped: capture now keeps the first copy of a repeated uuid and reports how many later copies it dropped. The per-batch log now counts them. Desktop always generates its own uuids, so this should never fire. The count is there in case an SDK bug causes duplicates.Empty event names are already dropped locally on desktop (
_hasName), so nothing changed there.💚 How did you test it?
phx_tokens, the error string leaves out the response body, andcaptureV1Resultssumsduplicates_dropped.flutter analyzeclean,flutter testall passing (1376).📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Claude Code checked the v6 desktop stack against the capture v1 SDK requirements and made these fixes. The
phx_check lives inPosthog.setupnext to the blank-token check, not in the desktop client. That means it also applies on web. That's fine: capture rejects these keys anyway, so nothing that works today stops working.🤖 Generated with Claude Code