Repository navigation
feat: add capture_ai() on an isolated AI capture lane - #250
Merged
Merged
Conversation
Add `capture_ai`, `capture_ai_batch`, `capture_ai_immediate`, `capture_ai_batch_immediate`, and the global `capture_ai`, posting to `POST /i/v1/ai/events` on a second transport lane: its own worker thread, queue, and retry state, started on the first call. The lane pins zstd, batches by serialized bytes (5 MiB soft target, guarded check-before-append), and pre-drops events whose properties exceed the backend's 8 MiB ceiling, logging only the event name and byte size. The SDK never tests the event name; the backend's per-event verdicts reach `on_error`, which now reports the lane via `CaptureFailure::endpoint()`. A 2xx with per-event drops and no hook registered now logs one aggregate warning per batch on both lanes. The analytics lane is unchanged: the worker and pipeline are parameterized by `LaneConfig`, and `LaneConfig::analytics()` reproduces prior behavior. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 2 · PR risk: 0/10 |
Prompt To Fix All With AI### Issue 1
src/client/async_client.rs:180-185
**Shutdown races AI startup**
A first `capture_ai` call can observe the analytics transport as open just before `shutdown` snapshots the existing lanes. If shutdown closes only the analytics lane, `get_or_init` can then create a new AI transport with its own open state. The event is delivered after shutdown returns, and the new worker remains alive until `Drop`, violating the shutdown contract. The blocking client has the same race at `src/client/blocking.rs:183-188`; lazy initialization and lane snapshots need a shared lifecycle synchronization point.
### Issue 2
src/client/transport.rs:1080-1089
**Envelope bypasses request limit**
The 8 MiB admission check covers only serialized `properties`, while fields such as `event`, `distinct_id`, and group values are unbounded. An event with small properties but a large envelope can therefore pass this check and be sent alone even when its complete body exceeds the endpoint's 20 MiB request cap. This makes the documented request-size margin unreliable and can cause the background event to be rejected. Please add a total serialized-size guard or otherwise handle this case, with coverage using a large non-properties field.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat: add capture_ai() on an isolated AI..." | Re-trigger Greptile |
Add `capture_ai_compression` (mirrors `capture_compression`: unset sends AI bodies uncompressed, the setter takes one of the four codecs, zstd recommended) and `capture_ai_max_queue_size` (default 1000) so the AI lane's codec and queue depth are configured, not hardcoded; `capture_compression` and `max_queue_size` no longer touch the AI lane. Close the shutdown race: `ai_transport()` re-checks the analytics lane's closed mark after `get_or_init` and closes a lane spawned in the window, so a first `capture_ai` racing `shutdown` cannot leave a worker delivering after shutdown returns. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
marandaneto
reviewed
Sep 9, 2026
marandaneto
left a comment
Member
There was a problem hiding this comment.
Advisory review: two reproduced behavioral defects. Existing library and AI integration tests pass for both client flavors.
… reading lanes on shutdown Two review findings on the AI lane. `admit_live` now owns `buffer_since`, so an event carried into a fresh batch keeps an interval timer instead of waiting for the next threshold or flush. `shutdown` and `Drop` mark the analytics lane closed before reading the AI lane, so a first `capture_ai` racing shutdown is either drained by that shutdown or closes itself in the post-init re-check; previously the read came first and a lane initialized in between stayed open. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
marandaneto
reviewed
Sep 9, 2026
marandaneto
approved these changes
Sep 9, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
4 of 5 tasks
5 of 8 tasks
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.
Adds
capture_ai()so Rust callers can send LLM analytics events to the livePOST /i/v1/ai/eventsendpoint. AI events ride their own isolated transport lane, so a multi-MB AI event can never delay or displace analytics events. Targetsv1; all public API changes are additive.capture_ai,capture_ai_batch,capture_ai_immediate,capture_ai_batch_immediate, and the globalposthog_rs::capture_ai, mirroring the analytics entry points.Endpoint::CaptureAiandCaptureFailure::endpoint()so anon_errorhook can tell which lane failed.capture_ai_compression(mirrorscapture_compression; unset sends AI bodies uncompressed, zstd recommended) andcapture_ai_max_queue_size(default 1000).capture_ai*call, and drained byflush/shutdown/Dropalongside the analytics lane.propertiesexceed the backend's 8 MiB ceiling locally, logging only the event name and size.ai_event_too_bigverdict.capturenorcapture_aiinspects the event name; the backend decides routing and reports per event, and that verdict already reacheson_error.$ai_-prefixed custom names are legitimate analytics traffic, and the analytics lane will start refusing AI names once feat(capture): both v1 lanes refuse the other's events posthog#95466 lands.on_errorhook now logs one aggregate warning per batch on both lanes; it was silent before.🤖 Generated with Claude Code