Skip to content

feat!: add captureAi on the capture v1 AI endpoint - #884

Merged
turnipdabeets merged 19 commits into
feat/v4-capture-optionsfrom
feat/v4-capture-ai
Oct 28, 2026
Merged

turnipdabeets merged 19 commits into
feat/v4-capture-optionsfrom
feat/v4-capture-ai

Conversation

@turnipdabeets

@turnipdabeets turnipdabeets commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #882 (per-event options) and #881 (capture v1 transport), which are stacked on #871.

💡 Motivation and Context

Adds captureAi(...), which sends AI observability events ($ai_generation, $ai_span, $ai_trace, $ai_embedding, ...) to the capture v1 AI endpoint (POST /i/v1/ai/events). That endpoint accepts events whose properties are up to 8 MiB. It follows posthog-ios 4.0 (PostHog/posthog-ios#939) and the capture_ai lanes in posthog-rs, posthog-go and posthog-python, and Ingestion's goal of shipping an AI method with every v1 release. Flutter and React Native need it on both natives before they can expose AI capture.

  • captureAi takes the same arguments as capture (including options) and goes through the same enrichment, precedence, personless default and beforeSend. Only the queue differs. capture never reroutes $ai_* events; a captureAi name that doesn't start with $ai_ logs a debug line and is sent unchanged (the server reports misrouted_event).
  • It uses the capture v1 transport from feat!: send events through capture v1 and add captureAi #881: Bearer auth, v1 headers, per-event results, retry identity (kept per endpoint path, so the two lanes don't reset each other's attempt numbers), same-origin redirects, Retry-After and backoff. A 404 is terminal.
  • AI events go to their own disk queue (posthog-disk-ai-queue, PostHogConfig.aiStoragePrefix) with its own executor and retry state, created at setup so events left on disk by a previous launch still send, flushed by flush() and close(). A failing or slow AI lane never delays or drops analytics events.
  • captureAi returns the queued event's uuid, or null when the event isn't queued: disabled, opted out, dropped by beforeSend, or over the size limit.
  • The queue batches by bytes as well as count: at most about 5 MiB of encoded events per request, checked before appending, an oversize event alone, and a flush as soon as that much is queued. This applies to the analytics lane too. The AI queue keeps at most 100 events (captureAiMaxQueueSize) and 50 MiB on disk, dropping the oldest first. An AI event over captureAiMaxEventBytes (default and ceiling 8 MiB) plus a 64 KiB envelope, measured on the serialized event before encryption, is dropped in captureAi, which returns null and logs only the event name and size.
  • New PostHogConfig knobs, named after the server SDKs': captureAiCompression (gzip), captureAiMaxQueueSize (100), captureAiTimeout (60 s; AI requests use a derived OkHttp client with read and write idle timeouts, so a slow upload that is still making progress isn't cut off), captureAiMaxEventBytes (8 MiB, can only lower). Non-positive values fall back to the defaults.
  • Compliance adapter: POST /capture_ai, tracks /i/v1/ai/events, advertises capture_ai_v1, applies enable_compression to the AI lane too (the harness has no separate AI compression input), returns no uuid when captureAi returns null, and now forwards the harness timestamp on both capture routes (it never did, which failed the non-UTC timestamp scenario on feat!: send events through capture v1 and add captureAi #881).
  • With debug on, the AI request log names only the event count, never the body, since AI properties hold prompts and media. Each event capture v1 drops (analytics or AI) is logged with its name, uuid and the server's details, such as misrouted_event or ai_event_too_big, never its properties.
  • Changeset bullets and the guide's "AI events" section.

Differences from the spec and the other SDKs

The capture-ai spec applies to server SDKs. Where Android differs:

  • No uuid argument: Android capture doesn't take one either. captureAi does return the uuid.
  • Oversize events are dropped at the call, as in posthog-ios: captureAi serializes the event into a byte counter on the caller's thread, before encryption, and returns null when it's over the limit. That's one extra serialization per AI event, without holding a second copy in memory.
  • Queue created at setup, not lazily: an offline mobile queue has to send events persisted by an earlier launch.
  • Queue limit is 100 events and 50 MiB, not 1000 events: keeps the disk use of multi-MB events down on devices.
  • Gzip by default on the AI lane (captureAiCompression); the server SDKs send AI requests uncompressed by default.
  • Timeouts: OkHttp read and write idle timeouts of captureAiTimeout (connect stays at the default), close to iOS's single inactivity timeout.
  • The surveys and session replay onEvent hooks still fire for AI-lane events, as for analytics events.

💚 How did you test it?

  • ./gradlew apiCheck spotlessCheck :posthog:test :posthog-android:testReleaseUnitTest :posthog-server:test pass (core 1131, Android 765, server 609); the compliance adapter compiles.
  • New PostHogCaptureAiTest: routing by method (and capture not rerouting $ai_*), beforeSend and options, a 404 is terminal with no further request, an oversize event is dropped when queued with a name-and-size log only, requests split at the byte limit, the queue flushes once a batch's worth of bytes is queued before flushAt, the return value for sent/disabled/opted-out/rejected events, per-lane timeout and compression, separate retry identity per lane, flush()/close() drain the AI queue, a 2xx with retry results keeps only those files.
  • New PostHogQueueByteLimitsTest: byte accounting on add/load/remove, eviction of the oldest on add and on load (newest kept), check-before-append windows, oversize record alone. PostHogCaptureV1Test gained the AI path cases and the per-event drop log with details. PostHogCaptureAiTest also sends an event with exactly 8 MiB of properties.
  • Compliance harness 1.20.0 in CI: 214/219 across capture_v1, capture_ai_v1 and feature_flags. The 5 failures are the known ones: capture_ai_keeps_supplied_uuid (no uuid argument; its aborted flush also lands in event_has_required_root_fields_batch), geoip_disable_injected_into_properties on both suites (no SDK surface) and historical_migration_set_in_body (no historical-migration mode). The timestamp scenarios pass now that the adapter forwards timestamp.
  • Not done: docs for captureAi on posthog.com / LLM analytics; a device run against PostHog Cloud.

📝 Checklist

  • All commits are signed
  • 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 (bullets added to .changeset/android-v4-major.md)

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Built with Claude Code (Claude Fable 5.1). One subagent implemented the lane against the sdk-specs capture-ai spec, the v1 AI route contract, the harness capture_ai_v1 contract and the posthog-ios 4.0 branch; a review and triage pass fed fixes back. Maintainer decisions: a separate AI lane with a small disk queue and no fallback, the four config knobs named after the server SDKs, and the asynchronous size check.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DRbQPidntd4hVufNpMT19L

@posthog posthog Bot 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.

PostHog Review · Standard · reviewed c1c2add: 4 Should fix.

This is the only automatic review while this pull request is a draft. Comment @posthog review to review new changes sooner. Otherwise, a review of the new changes runs when you mark it ready for review.

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.

Comment thread posthog/src/main/java/com/posthog/internal/PostHogQueue.kt Outdated
Comment thread posthog/src/main/java/com/posthog/PostHog.kt
Comment thread posthog/src/main/java/com/posthog/internal/EndpointSpec.kt Outdated
@turnipdabeets
turnipdabeets force-pushed the feat/v4-capture-ai branch 3 times, most recently from e18d709 to 4dae7cb Compare October 24, 2026 18:30
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

posthog-android Compliance Report

Date: 2026-10-28T20:49:01.795818+00:00
Duration: 418100ms

⚠️ Some Tests Failed

213/219 tests passed, 6 failed


Capture_V1 Tests

⚠️ 99/101 tests passed, 2 failed

View Details
Test Status Duration
Endpoint And Method.Targets V1 Endpoint ✅ 433ms
Endpoint And Method.Does Not Use Legacy Endpoints ✅ 30ms
Required Headers.Has Authorization Bearer Header ✅ 31ms
Required Headers.Has Content Type Json ✅ 31ms
Required Headers.Has Posthog Sdk Info Format ✅ 25ms
Required Headers.Has Posthog Attempt Header ✅ 26ms
Required Headers.Has Posthog Request Id ✅ 25ms
Required Headers.Has Posthog Request Timestamp ✅ 23ms
Required Headers.Has User Agent ✅ 27ms
Body Format.Body Has Created At And Batch ✅ 28ms
Body Format.No Api Key In Body ✅ 28ms
Body Format.No Sent At In Body ✅ 22ms
Event Format.Event Has Required Root Fields ✅ 26ms
Event Format.Event Uuid Is Valid ✅ 31ms
Event Format.Event Timestamp Is Rfc3339 ✅ 24ms
Event Format.Non Utc Event Timestamp Is Converted To Utc ✅ 27ms
Event Format.Distinct Id Is String ✅ 20ms
Event Format.Distinct Id At Root Not Properties ✅ 23ms
Event Format.Custom Properties Preserved ✅ 27ms
Event Format.Set Properties Preserved ✅ 26ms
Event Format.Set Once Properties Preserved ✅ 25ms
Event Format.Groups Properties Preserved ✅ 23ms
Event Format.Sdk Generates Uuid If Not Provided ✅ 25ms
Event Format.Event Has Required Root Fields Batch ✅ 31ms
Event Format.Event Uuid Is Valid Batch ✅ 30ms
Event Format.Event Timestamp Is Rfc3339 Batch ✅ 26ms
Event Format.Distinct Id Is String Batch ✅ 28ms
Event Format.Distinct Id At Root Not Properties Batch ✅ 26ms
Event Format.Custom Properties Preserved Batch ✅ 26ms
Event Format.Set Properties Preserved Batch ✅ 24ms
Event Format.Set Once Properties Preserved Batch ✅ 29ms
Event Format.Groups Properties Preserved Batch ✅ 26ms
Event Format.Sdk Generates Uuid If Not Provided Batch ✅ 28ms
Batch Behavior.Multiple Events In Single Batch ✅ 51ms
Batch Behavior.Batch Envelope Smoke ✅ 34ms
Batch Behavior.Flush With No Events Sends Nothing ✅ 13ms
Batch Behavior.Flush At Triggers Batch ✅ 1020ms
Batch Behavior.Created At Reflects Batch Creation Time ✅ 20ms
Deduplication.Generates Unique Uuids ✅ 41ms
Deduplication.Different Events Same Content Different Uuids ✅ 20ms
Deduplication.Preserves Uuid On Retry ✅ 7012ms
Deduplication.Preserves Timestamp On Retry ✅ 7017ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 7021ms
Deduplication.No Duplicate Events In Batch ✅ 31ms
Header Behavior On Retry.Attempt Header Starts At One ✅ 16ms
Header Behavior On Retry.Attempt Header Increments On Retry ✅ 12027ms
Header Behavior On Retry.Request Id Preserved On Retry ✅ 7015ms
Header Behavior On Retry.Different Requests Have Different Request Ids ✅ 2028ms
Header Behavior On Retry.Request Timestamp Changes On Retry ✅ 7017ms
Response Format Validation.Success Response Has Uuid Keyed Results ✅ 17ms
Response Format Validation.Success Response Has Ok For Each Event ✅ 22ms
Response Format Validation.Success No Retry After When All Ok ✅ 20ms
Response Format Validation.Success Retry After Present When Retry Events ✅ 21ms
Response Format Validation.Success No Retry After When Drop Only ✅ 19ms
Response Format Validation.Response Echoes Request Id ✅ 18ms
Retry Behavior.Retries On 408 ✅ 7015ms
Retry Behavior.Retries On 500 ✅ 7017ms
Retry Behavior.Retries On 503 ✅ 7024ms
Retry Behavior.Retries On 504 ✅ 7017ms
Retry Behavior.Retryable Errors Have Retry After ✅ 4013ms
Retry Behavior.Respects Retry After On Retryable Error ✅ 10025ms
Retry Behavior.Does Not Retry On 400 ✅ 4022ms
Retry Behavior.Does Not Retry On 401 ✅ 4018ms
Retry Behavior.Does Not Retry On 402 ✅ 4018ms
Retry Behavior.Does Not Retry On 413 ✅ 4018ms
Retry Behavior.Does Not Retry On 415 ✅ 4017ms
Retry Behavior.Non Retryable Errors Have No Retry After ✅ 4021ms
Retry Behavior.Implements Backoff ✅ 17030ms
Retry Behavior.Max Retries Respected ✅ 17020ms
Partial Batch Handling.Handles 200 Full Success ✅ 2020ms
Partial Batch Handling.Handles 200 With All Ok ✅ 3024ms
Partial Batch Handling.Does Not Retry Dropped Events ✅ 3023ms
Partial Batch Handling.Does Not Retry Limited Events ✅ 3025ms
Partial Batch Handling.Prunes Ok Events On Partial Retry ✅ 5024ms
Partial Batch Handling.Prunes Dropped Events On Partial Retry ✅ 5024ms
Partial Batch Handling.Retries Only Retry Events From Partial ✅ 5027ms
Partial Batch Handling.Partial Retry Preserves Uuids ✅ 5022ms
Partial Batch Handling.Partial Retry Attempt Header Increments ✅ 5026ms
Partial Batch Handling.Partial Retry Request Id Preserved ✅ 5025ms
Partial Batch Handling.Respects Retry After On Partial ✅ 5023ms
Partial Batch Handling.Unknown Result Treated As Terminal ✅ 3021ms
Partial Batch Handling.Mixed Ok Drop Limited No Retry ✅ 3028ms
Compression.Sends Gzip Content Encoding ✅ 14ms
Compression.No Content Encoding When Disabled ✅ 14ms
Compression.Compressed Body Is Decompressible ✅ 14ms
Error Handling.Does Not Retry On Unknown 4Xx ✅ 4016ms
Event Options.Cookieless Mode Override ✅ 16ms
Event Options.Disable Skew Correction Override ✅ 12ms
Event Options.Process Person Profile Override ✅ 12ms
Event Options.Product Tour Id Override ✅ 14ms
Event Options.Unset Options Omitted ✅ 15ms
Event Options.Options Override In Batch ✅ 20ms
Event Options.Unknown Option Passes Through ✅ 16ms
Event Options.Option Value Is Not Converted ✅ 16ms
Event Options.Lenient Bool Option Values Are Not Converted ✅ 15ms
Event Options.Option Wins Over Legacy Property ✅ 18ms
Event Options.Legacy Property Fills Unset Option ✅ 18ms
Event Options.Null Option Falls Back To Legacy Property ✅ 17ms
Geoip And Historical Migration.Geoip Disable Injected Into Properties ❌ 14ms
Geoip And Historical Migration.Historical Migration Set In Body ❌ 16ms
Geoip And Historical Migration.Historical Migration Absent By Default ✅ 15ms

Failures

geoip_and_historical_migration.geoip_disable_injected_into_properties

Expected $geoip_disable='True', got 'None'. Available properties: ['$is_identified', '$recording_status', '$sdk_debug_pending_queue_size']

geoip_and_historical_migration.historical_migration_set_in_body

Body missing 'historical_migration' field. Body keys: ['created_at', 'batch']

Capture_Ai_V1 Tests

⚠️ 97/101 tests passed, 4 failed

View Details
Test Status Duration
Endpoint And Method.Targets V1 Endpoint ✅ 16ms
Endpoint And Method.Does Not Use Legacy Endpoints ✅ 14ms
Endpoint And Method.Capture Does Not Reroute Ai Named Events ✅ 15ms
Required Headers.Has Authorization Bearer Header ✅ 15ms
Required Headers.Has Content Type Json ✅ 15ms
Required Headers.Has Posthog Sdk Info Format ✅ 15ms
Required Headers.Has Posthog Attempt Header ✅ 16ms
Required Headers.Has Posthog Request Id ✅ 16ms
Required Headers.Has Posthog Request Timestamp ✅ 16ms
Required Headers.Has User Agent ✅ 21ms
Body Format.Body Has Created At And Batch ✅ 26ms
Body Format.No Api Key In Body ✅ 20ms
Body Format.No Sent At In Body ✅ 16ms
Event Format.Event Has Required Root Fields ✅ 18ms
Event Format.Event Uuid Is Valid ✅ 19ms
Event Format.Event Timestamp Is Rfc3339 ✅ 16ms
Event Format.Non Utc Event Timestamp Is Converted To Utc ✅ 21ms
Event Format.Distinct Id Is String ✅ 19ms
Event Format.Distinct Id At Root Not Properties ✅ 19ms
Event Format.Custom Properties Preserved ✅ 22ms
Event Format.Set Properties Preserved ✅ 20ms
Event Format.Set Once Properties Preserved ✅ 22ms
Event Format.Groups Properties Preserved ✅ 17ms
Event Format.Sdk Generates Uuid If Not Provided ✅ 15ms
Event Format.Capture Ai Keeps Supplied Uuid ❌ 9ms
Event Format.Event Has Required Root Fields Batch ❌ 19ms
Event Format.Event Uuid Is Valid Batch ✅ 33ms
Event Format.Event Timestamp Is Rfc3339 Batch ✅ 24ms
Event Format.Distinct Id Is String Batch ✅ 30ms
Event Format.Distinct Id At Root Not Properties Batch ✅ 21ms
Event Format.Custom Properties Preserved Batch ✅ 25ms
Event Format.Set Properties Preserved Batch ✅ 29ms
Event Format.Set Once Properties Preserved Batch ✅ 35ms
Event Format.Groups Properties Preserved Batch ✅ 28ms
Event Format.Sdk Generates Uuid If Not Provided Batch ✅ 25ms
Batch Behavior.Multiple Events In Single Batch ✅ 33ms
Batch Behavior.Batch Envelope Smoke ✅ 30ms
Batch Behavior.Flush At Triggers Batch ✅ 1015ms
Batch Behavior.Created At Reflects Batch Creation Time ✅ 14ms
Deduplication.Generates Unique Uuids ✅ 27ms
Deduplication.Different Events Same Content Different Uuids ✅ 20ms
Deduplication.Preserves Uuid On Retry ✅ 7015ms
Deduplication.Preserves Timestamp On Retry ✅ 7015ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 7015ms
Deduplication.No Duplicate Events In Batch ✅ 24ms
Header Behavior On Retry.Attempt Header Starts At One ✅ 12ms
Header Behavior On Retry.Attempt Header Increments On Retry ✅ 12014ms
Header Behavior On Retry.Request Id Preserved On Retry ✅ 7015ms
Header Behavior On Retry.Different Requests Have Different Request Ids ✅ 2024ms
Header Behavior On Retry.Request Timestamp Changes On Retry ✅ 7011ms
Response Format Validation.Success Response Has Uuid Keyed Results ✅ 15ms
Response Format Validation.Success Response Has Ok For Each Event ✅ 19ms
Response Format Validation.Success No Retry After When All Ok ✅ 15ms
Response Format Validation.Success Retry After Present When Retry Events ✅ 16ms
Response Format Validation.Success No Retry After When Drop Only ✅ 17ms
Response Format Validation.Response Echoes Request Id ✅ 14ms
Retry Behavior.Retries On 408 ✅ 7015ms
Retry Behavior.Retries On 500 ✅ 7011ms
Retry Behavior.Retries On 503 ✅ 7021ms
Retry Behavior.Retries On 504 ✅ 7015ms
Retry Behavior.Retryable Errors Have Retry After ✅ 4012ms
Retry Behavior.Respects Retry After On Retryable Error ✅ 10022ms
Retry Behavior.Does Not Retry On 400 ✅ 4015ms
Retry Behavior.Does Not Retry On 401 ✅ 4017ms
Retry Behavior.Does Not Retry On 402 ✅ 4019ms
Retry Behavior.Does Not Retry On 413 ✅ 4017ms
Retry Behavior.Does Not Retry On 415 ✅ 4015ms
Retry Behavior.Non Retryable Errors Have No Retry After ✅ 4016ms
Retry Behavior.Implements Backoff ✅ 17015ms
Retry Behavior.Max Retries Respected ❌ 17029ms
Partial Batch Handling.Handles 200 Full Success ✅ 2015ms
Partial Batch Handling.Handles 200 With All Ok ✅ 3021ms
Partial Batch Handling.Does Not Retry Dropped Events ✅ 3020ms
Partial Batch Handling.Does Not Retry Limited Events ✅ 3020ms
Partial Batch Handling.Prunes Ok Events On Partial Retry ✅ 5024ms
Partial Batch Handling.Prunes Dropped Events On Partial Retry ✅ 5026ms
Partial Batch Handling.Retries Only Retry Events From Partial ✅ 5024ms
Partial Batch Handling.Partial Retry Preserves Uuids ✅ 5027ms
Partial Batch Handling.Partial Retry Attempt Header Increments ✅ 5021ms
Partial Batch Handling.Partial Retry Request Id Preserved ✅ 5022ms
Partial Batch Handling.Respects Retry After On Partial ✅ 5022ms
Partial Batch Handling.Unknown Result Treated As Terminal ✅ 3019ms
Partial Batch Handling.Mixed Ok Drop Limited No Retry ✅ 3024ms
Compression.Sends Gzip Content Encoding ✅ 14ms
Compression.No Content Encoding When Disabled ✅ 14ms
Compression.Compressed Body Is Decompressible ✅ 11ms
Error Handling.Does Not Retry On Unknown 4Xx ✅ 4013ms
Event Options.Cookieless Mode Override ✅ 13ms
Event Options.Disable Skew Correction Override ✅ 15ms
Event Options.Process Person Profile Override ✅ 13ms
Event Options.Product Tour Id Override ✅ 13ms
Event Options.Unset Options Omitted ✅ 18ms
Event Options.Options Override In Batch ✅ 18ms
Event Options.Unknown Option Passes Through ✅ 12ms
Event Options.Option Value Is Not Converted ✅ 12ms
Event Options.Lenient Bool Option Values Are Not Converted ✅ 11ms
Event Options.Option Wins Over Legacy Property ✅ 17ms
Event Options.Legacy Property Fills Unset Option ✅ 13ms
Event Options.Null Option Falls Back To Legacy Property ✅ 13ms
Geoip And Historical Migration.Geoip Disable Injected Into Properties ❌ 11ms
Geoip And Historical Migration.Historical Migration Absent By Default ✅ 13ms

Failures

event_format.capture_ai_keeps_supplied_uuid

Expected last action result '0198c0de-0000-7000-8000-000000000abc', got '01a18442-cf9e-70bc-907b-3d217ce253d7'

event_format.event_has_required_root_fields_batch

Expected 1 requests, got 2

retry_behavior.max_retries_respected

Expected 4 requests, got 5

geoip_and_historical_migration.geoip_disable_injected_into_properties

Expected $geoip_disable='True', got 'None'. Available properties: ['$is_identified', '$recording_status', '$sdk_debug_session_start', '$sdk_debug_pending_queue_size']

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 27ms
Request Payload.Flags Request Uses V2 Query Param ✅ 18ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 18ms
Request Payload.Flags Request Omits Authorization Header ✅ 21ms
Request Payload.Token In Flags Body Matches Init ✅ 15ms
Request Payload.Groups Round Trip ✅ 18ms
Request Payload.Groups Default To Empty Object ✅ 17ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 19ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 20ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 19ms
Request Lifecycle.No Flags Request On Init Alone ✅ 6ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 12ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 24ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 16ms
Retry Behavior.Retries Flags On 502 ✅ 323ms
Retry Behavior.Retries Flags On 504 ✅ 316ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 15ms

@turnipdabeets
turnipdabeets added this pull request to stack #888 October 26, 2026 18:03
@turnipdabeets
turnipdabeets removed this pull request from stack #888 October 26, 2026 18:06
@turnipdabeets
turnipdabeets added this pull request to stack #890 October 26, 2026 18:06
@turnipdabeets
turnipdabeets marked this pull request as ready for review October 26, 2026 18:45
@turnipdabeets
turnipdabeets requested a review from a team as a code owner October 26, 2026 18:45
Comment thread posthog/src/main/java/com/posthog/internal/PostHogQueue.kt Outdated
Comment thread posthog/src/main/java/com/posthog/PostHogConfig.kt Outdated
Comment thread docs/migration-3.x-to-4.0.md Outdated
Comment thread docs/migration-3.x-to-4.0.md Outdated

@posthog posthog Bot 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.

PostHog Review · Standard · reviewed 4ffa819: 2 Should fix.

Comment thread posthog/src/main/java/com/posthog/PostHog.kt Outdated
Comment thread posthog/src/main/java/com/posthog/internal/EndpointSpec.kt Outdated
Comment thread posthog/src/main/java/com/posthog/internal/EndpointSpec.kt
@veria-ai

veria-ai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

turnipdabeets and others added 12 commits October 27, 2026 18:43
`captureAi` takes the same arguments as `capture`, goes through the same
enrichment, precedence, personless default and `beforeSend`, and queues the
event on its own disk queue, executor and retry identity for
`POST /i/v1/ai/events`. It returns the event's uuid once it's handed to the
queue, or null when the SDK is disabled or opted out or `beforeSend` dropped
it. A name not starting with `$ai_` logs one debug line and is sent unchanged;
`capture` never reroutes `$ai_*` events. The AI request client uses
`captureAiTimeout` for its call, read and write timeouts and honors
`captureAiCompression` through its own gzip interceptor instance.

`PostHogQueue` becomes byte-aware through `EndpointSpec`: `maxBatchBytes`
bounds a request window (check before append, an oversize record goes alone)
and flushes as soon as that much is queued; `maxQueueBytes` is a FIFO disk cap
on top of `maxQueueSize`, enforced on add and on load; `maxRecordBytes` drops a
record over the limit when it's queued, logging only its name and size. The
analytics spec gets a 5 MiB request cap; the AI spec gets 5 MiB requests,
50 MiB on disk and `captureAiMaxEventBytes` (at most 8 MiB) plus a 64 KiB
envelope per event.

New `PostHogConfig` knobs: `captureAiCompression`, `captureAiMaxQueueSize`,
`captureAiTimeout` and `captureAiMaxEventBytes`; non-positive values use the
defaults. `PostHogAndroid` stores the AI queue under `posthog-disk-ai-queue`.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DRbQPidntd4hVufNpMT19L
SDK-level: routing by method, beforeSend and options, null returns, a
terminal 404, the oversize drop (name and size only in the log), the non-`$ai_`
debug line, flush and close draining the AI queue, compression per lane and the
AI timeout. API-level: the AI endpoint's headers and body, separate retry
identities per lane, and an AI queue keeping only the events asked to retry.
Queue-level, on a synthetic record: byte-limited request windows, an oversize
record sent alone, the flush at a request's worth of bytes, the per-record
drop, the disk cap on add and on load, and the spec limits and config
fallbacks. The Android setup test covers the AI storage prefix.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DRbQPidntd4hVufNpMT19L
Adds `POST /capture_ai`, which routes through `captureAi` and returns the uuid
it sent, tracks `/i/v1/ai/events` requests next to the analytics ones and
advertises `capture_ai_v1`. Also restores the capture v1 adapter changes
(tracking by capture path, `PostHog-Attempt` as the retry count,
`personProfiles = ALWAYS`, the renamed push retries field) that the options
commit reverted.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DRbQPidntd4hVufNpMT19L
The queue measured an AI event after `encryption`, so an encryption that
grows the data dropped events PostHog would accept, and captureAi had
already returned the uuid. captureAi now measures the serialized event
before encryption and returns null when it's over the limit. The queue's
per-record limit had no other user and is removed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The limit covers an AI event's properties up to 8 MiB, with 64 KiB for the
rest of the serialized event, measured before encryption. captureAiTimeout
is a timeout without network activity, not a call timeout.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Debug logs named only the drop count, so a misrouted_event or
ai_event_too_big drop was invisible. Each dropped event is now logged with
its name, uuid and the server's details, never its properties. Adds a test
that an AI event with exactly 8 MiB of properties is sent.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
captureAi checks the size on the caller's thread, so a huge prompt or
base64 payload no longer costs a full serialization just to be dropped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Properties can hold prompts and other user content.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
turnipdabeets and others added 2 commits October 28, 2026 10:48
java.util.Base64 isn't on API 23, so animalsnifferTest failed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The data class hashCode calls Double.hashCode(double), which isn't on API 23.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Comment thread posthog/src/test/java/com/posthog/PostHogCaptureAiTest.kt Fixed

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

Earlier size-admission, logging and signature-check issues are addressed, and the current delivery controls pass. Only two optional documentation and regression-test improvements remain.

AI-assisted review.

Comment thread docs/migration-3.x-to-4.0.md Outdated
Comment thread posthog/src/test/java/com/posthog/internal/PostHogSerializerTest.kt Outdated
turnipdabeets and others added 4 commits October 28, 2026 16:14
* fix: retry capture v1 requests that get 404 or 429

A 404 usually means a misconfigured proxy and a 429 now only comes from an overloaded ingress, so keep the events queued and retry them with the usual backoff and Retry-After handling instead of dropping them. Matches PostHog/posthog-go#395.

* fix: warn once when capture v1 returns 404

A 404 now keeps events queued, so nothing tells the app that the route
is missing. Print one warning per client, even without debug, naming the
URL and the path a reverse proxy or self-hosted instance must serve.

* fix: leave host credentials and query out of the capture v1 404 warning

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Match posthog-ios, posthog-python and the Flutter desktop lane: the value
is the serialized event size, defaulting to 8 MiB + 64 KiB, instead of a
properties size the SDK added 64 KiB to. A value over the ceiling now
logs a warning when it is clamped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@turnipdabeets
turnipdabeets merged commit 83e9b67 into main Oct 28, 2026
17 checks passed
@turnipdabeets
turnipdabeets deleted the feat/v4-capture-ai branch October 28, 2026 20:58
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.

4 participants