Repository navigation
fix: retry capture v1 requests that get 404 or 429 - #897
Conversation
posthog-android Compliance ReportDate: 2026-10-28T14:59:52.481779+00:00
|
| Test | Status | Duration |
|---|---|---|
| Endpoint And Method.Targets V1 Endpoint | ✅ | 434ms |
| Endpoint And Method.Does Not Use Legacy Endpoints | ✅ | 43ms |
| Required Headers.Has Authorization Bearer Header | ✅ | 33ms |
| Required Headers.Has Content Type Json | ✅ | 35ms |
| Required Headers.Has Posthog Sdk Info Format | ✅ | 30ms |
| Required Headers.Has Posthog Attempt Header | ✅ | 26ms |
| Required Headers.Has Posthog Request Id | ✅ | 29ms |
| Required Headers.Has Posthog Request Timestamp | ✅ | 36ms |
| Required Headers.Has User Agent | ✅ | 28ms |
| Body Format.Body Has Created At And Batch | ✅ | 28ms |
| Body Format.No Api Key In Body | ✅ | 27ms |
| Body Format.No Sent At In Body | ✅ | 27ms |
| Event Format.Event Has Required Root Fields | ✅ | 27ms |
| Event Format.Event Uuid Is Valid | ✅ | 27ms |
| Event Format.Event Timestamp Is Rfc3339 | ✅ | 25ms |
| Event Format.Non Utc Event Timestamp Is Converted To Utc | ✅ | 27ms |
| Event Format.Distinct Id Is String | ✅ | 25ms |
| Event Format.Distinct Id At Root Not Properties | ✅ | 26ms |
| Event Format.Custom Properties Preserved | ✅ | 23ms |
| Event Format.Set Properties Preserved | ✅ | 29ms |
| Event Format.Set Once Properties Preserved | ✅ | 26ms |
| Event Format.Groups Properties Preserved | ✅ | 26ms |
| Event Format.Sdk Generates Uuid If Not Provided | ✅ | 26ms |
| Event Format.Event Has Required Root Fields Batch | ✅ | 37ms |
| Event Format.Event Uuid Is Valid Batch | ✅ | 37ms |
| Event Format.Event Timestamp Is Rfc3339 Batch | ✅ | 31ms |
| Event Format.Distinct Id Is String Batch | ✅ | 36ms |
| Event Format.Distinct Id At Root Not Properties Batch | ✅ | 29ms |
| Event Format.Custom Properties Preserved Batch | ✅ | 33ms |
| Event Format.Set Properties Preserved Batch | ✅ | 30ms |
| Event Format.Set Once Properties Preserved Batch | ✅ | 35ms |
| Event Format.Groups Properties Preserved Batch | ✅ | 34ms |
| Event Format.Sdk Generates Uuid If Not Provided Batch | ✅ | 33ms |
| Batch Behavior.Multiple Events In Single Batch | ✅ | 52ms |
| Batch Behavior.Batch Envelope Smoke | ✅ | 36ms |
| Batch Behavior.Flush With No Events Sends Nothing | ✅ | 24ms |
| Batch Behavior.Flush At Triggers Batch | ✅ | 1027ms |
| Batch Behavior.Created At Reflects Batch Creation Time | ✅ | 29ms |
| Deduplication.Generates Unique Uuids | ✅ | 46ms |
| Deduplication.Different Events Same Content Different Uuids | ✅ | 28ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 7016ms |
| Deduplication.Preserves Timestamp On Retry | ✅ | 7017ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 7025ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 35ms |
| Header Behavior On Retry.Attempt Header Starts At One | ✅ | 19ms |
| Header Behavior On Retry.Attempt Header Increments On Retry | ✅ | 12032ms |
| Header Behavior On Retry.Request Id Preserved On Retry | ✅ | 7016ms |
| Header Behavior On Retry.Different Requests Have Different Request Ids | ✅ | 2031ms |
| Header Behavior On Retry.Request Timestamp Changes On Retry | ✅ | 7016ms |
| Response Format Validation.Success Response Has Uuid Keyed Results | ✅ | 17ms |
| Response Format Validation.Success Response Has Ok For Each Event | ✅ | 23ms |
| Response Format Validation.Success No Retry After When All Ok | ✅ | 22ms |
| Response Format Validation.Success Retry After Present When Retry Events | ✅ | 27ms |
| Response Format Validation.Success No Retry After When Drop Only | ✅ | 23ms |
| Response Format Validation.Response Echoes Request Id | ✅ | 17ms |
| Retry Behavior.Retries On 408 | ✅ | 7016ms |
| Retry Behavior.Retries On 500 | ✅ | 7016ms |
| Retry Behavior.Retries On 503 | ✅ | 7024ms |
| Retry Behavior.Retries On 504 | ✅ | 7016ms |
| Retry Behavior.Retryable Errors Have Retry After | ✅ | 4013ms |
| Retry Behavior.Respects Retry After On Retryable Error | ✅ | 10029ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 4023ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 4018ms |
| Retry Behavior.Does Not Retry On 402 | ✅ | 4021ms |
| Retry Behavior.Does Not Retry On 413 | ✅ | 4021ms |
| Retry Behavior.Does Not Retry On 415 | ✅ | 4018ms |
| Retry Behavior.Non Retryable Errors Have No Retry After | ✅ | 4021ms |
| Retry Behavior.Implements Backoff | ✅ | 17031ms |
| Retry Behavior.Max Retries Respected | ❌ | 17039ms |
| Partial Batch Handling.Handles 200 Full Success | ✅ | 2017ms |
| Partial Batch Handling.Handles 200 With All Ok | ✅ | 3024ms |
| Partial Batch Handling.Does Not Retry Dropped Events | ✅ | 3028ms |
| Partial Batch Handling.Does Not Retry Limited Events | ✅ | 3027ms |
| Partial Batch Handling.Prunes Ok Events On Partial Retry | ✅ | 5023ms |
| Partial Batch Handling.Prunes Dropped Events On Partial Retry | ✅ | 5025ms |
| Partial Batch Handling.Retries Only Retry Events From Partial | ✅ | 5029ms |
| Partial Batch Handling.Partial Retry Preserves Uuids | ✅ | 5028ms |
| Partial Batch Handling.Partial Retry Attempt Header Increments | ✅ | 5025ms |
| Partial Batch Handling.Partial Retry Request Id Preserved | ✅ | 5029ms |
| Partial Batch Handling.Respects Retry After On Partial | ✅ | 5024ms |
| Partial Batch Handling.Unknown Result Treated As Terminal | ✅ | 3022ms |
| Partial Batch Handling.Mixed Ok Drop Limited No Retry | ✅ | 3032ms |
| Compression.Sends Gzip Content Encoding | ✅ | 15ms |
| Compression.No Content Encoding When Disabled | ✅ | 14ms |
| Compression.Compressed Body Is Decompressible | ✅ | 17ms |
| Error Handling.Does Not Retry On Unknown 4Xx | ✅ | 4019ms |
| Event Options.Cookieless Mode Override | ✅ | 15ms |
| Event Options.Disable Skew Correction Override | ✅ | 11ms |
| Event Options.Process Person Profile Override | ✅ | 16ms |
| Event Options.Product Tour Id Override | ✅ | 14ms |
| Event Options.Unset Options Omitted | ✅ | 14ms |
| Event Options.Options Override In Batch | ✅ | 19ms |
| Event Options.Unknown Option Passes Through | ✅ | 17ms |
| Event Options.Option Value Is Not Converted | ✅ | 15ms |
| Event Options.Lenient Bool Option Values Are Not Converted | ✅ | 18ms |
| Event Options.Option Wins Over Legacy Property | ✅ | 15ms |
| Event Options.Legacy Property Fills Unset Option | ✅ | 16ms |
| Event Options.Null Option Falls Back To Legacy Property | ✅ | 17ms |
| Geoip And Historical Migration.Geoip Disable Injected Into Properties | ❌ | 19ms |
| Geoip And Historical Migration.Historical Migration Set In Body | ❌ | 17ms |
| Geoip And Historical Migration.Historical Migration Absent By Default | ✅ | 13ms |
Failures
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_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
View Details
| Test | Status | Duration |
|---|---|---|
| Endpoint And Method.Targets V1 Endpoint | ✅ | 20ms |
| Endpoint And Method.Does Not Use Legacy Endpoints | ✅ | 16ms |
| Endpoint And Method.Capture Does Not Reroute Ai Named Events | ✅ | 18ms |
| Required Headers.Has Authorization Bearer Header | ✅ | 15ms |
| Required Headers.Has Content Type Json | ✅ | 16ms |
| Required Headers.Has Posthog Sdk Info Format | ✅ | 15ms |
| Required Headers.Has Posthog Attempt Header | ✅ | 14ms |
| Required Headers.Has Posthog Request Id | ✅ | 14ms |
| Required Headers.Has Posthog Request Timestamp | ✅ | 17ms |
| Required Headers.Has User Agent | ✅ | 18ms |
| Body Format.Body Has Created At And Batch | ✅ | 21ms |
| Body Format.No Api Key In Body | ✅ | 20ms |
| Body Format.No Sent At In Body | ✅ | 18ms |
| Event Format.Event Has Required Root Fields | ✅ | 26ms |
| Event Format.Event Uuid Is Valid | ✅ | 22ms |
| Event Format.Event Timestamp Is Rfc3339 | ✅ | 20ms |
| Event Format.Non Utc Event Timestamp Is Converted To Utc | ✅ | 20ms |
| Event Format.Distinct Id Is String | ✅ | 18ms |
| Event Format.Distinct Id At Root Not Properties | ✅ | 16ms |
| Event Format.Custom Properties Preserved | ✅ | 17ms |
| Event Format.Set Properties Preserved | ✅ | 18ms |
| Event Format.Set Once Properties Preserved | ✅ | 18ms |
| Event Format.Groups Properties Preserved | ✅ | 16ms |
| Event Format.Sdk Generates Uuid If Not Provided | ✅ | 21ms |
| Event Format.Capture Ai Keeps Supplied Uuid | ❌ | 15ms |
| Event Format.Event Has Required Root Fields Batch | ❌ | 34ms |
| Event Format.Event Uuid Is Valid Batch | ✅ | 37ms |
| Event Format.Event Timestamp Is Rfc3339 Batch | ✅ | 30ms |
| Event Format.Distinct Id Is String Batch | ✅ | 24ms |
| Event Format.Distinct Id At Root Not Properties Batch | ✅ | 28ms |
| Event Format.Custom Properties Preserved Batch | ✅ | 31ms |
| Event Format.Set Properties Preserved Batch | ✅ | 29ms |
| Event Format.Set Once Properties Preserved Batch | ✅ | 35ms |
| Event Format.Groups Properties Preserved Batch | ✅ | 35ms |
| Event Format.Sdk Generates Uuid If Not Provided Batch | ✅ | 32ms |
| Batch Behavior.Multiple Events In Single Batch | ✅ | 44ms |
| Batch Behavior.Batch Envelope Smoke | ✅ | 38ms |
| Batch Behavior.Flush At Triggers Batch | ✅ | 1022ms |
| Batch Behavior.Created At Reflects Batch Creation Time | ✅ | 14ms |
| Deduplication.Generates Unique Uuids | ✅ | 30ms |
| Deduplication.Different Events Same Content Different Uuids | ✅ | 19ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 7014ms |
| Deduplication.Preserves Timestamp On Retry | ✅ | 7015ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 7015ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 30ms |
| Header Behavior On Retry.Attempt Header Starts At One | ✅ | 17ms |
| Header Behavior On Retry.Attempt Header Increments On Retry | ✅ | 12025ms |
| Header Behavior On Retry.Request Id Preserved On Retry | ✅ | 7013ms |
| Header Behavior On Retry.Different Requests Have Different Request Ids | ✅ | 2025ms |
| Header Behavior On Retry.Request Timestamp Changes On Retry | ✅ | 7016ms |
| Response Format Validation.Success Response Has Uuid Keyed Results | ✅ | 17ms |
| Response Format Validation.Success Response Has Ok For Each Event | ✅ | 24ms |
| Response Format Validation.Success No Retry After When All Ok | ✅ | 18ms |
| Response Format Validation.Success Retry After Present When Retry Events | ✅ | 23ms |
| Response Format Validation.Success No Retry After When Drop Only | ✅ | 20ms |
| Response Format Validation.Response Echoes Request Id | ✅ | 15ms |
| Retry Behavior.Retries On 408 | ✅ | 7021ms |
| Retry Behavior.Retries On 500 | ✅ | 7015ms |
| Retry Behavior.Retries On 503 | ✅ | 7021ms |
| Retry Behavior.Retries On 504 | ✅ | 7016ms |
| Retry Behavior.Retryable Errors Have Retry After | ✅ | 4013ms |
| Retry Behavior.Respects Retry After On Retryable Error | ✅ | 10021ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 4017ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 4025ms |
| Retry Behavior.Does Not Retry On 402 | ✅ | 4020ms |
| Retry Behavior.Does Not Retry On 413 | ✅ | 4018ms |
| Retry Behavior.Does Not Retry On 415 | ✅ | 4016ms |
| Retry Behavior.Non Retryable Errors Have No Retry After | ✅ | 4017ms |
| Retry Behavior.Implements Backoff | ✅ | 17027ms |
| Retry Behavior.Max Retries Respected | ❌ | 17032ms |
| Partial Batch Handling.Handles 200 Full Success | ✅ | 2017ms |
| Partial Batch Handling.Handles 200 With All Ok | ✅ | 3023ms |
| Partial Batch Handling.Does Not Retry Dropped Events | ✅ | 3026ms |
| Partial Batch Handling.Does Not Retry Limited Events | ✅ | 3019ms |
| Partial Batch Handling.Prunes Ok Events On Partial Retry | ✅ | 5027ms |
| Partial Batch Handling.Prunes Dropped Events On Partial Retry | ✅ | 5025ms |
| Partial Batch Handling.Retries Only Retry Events From Partial | ✅ | 5027ms |
| Partial Batch Handling.Partial Retry Preserves Uuids | ✅ | 5024ms |
| Partial Batch Handling.Partial Retry Attempt Header Increments | ✅ | 5027ms |
| Partial Batch Handling.Partial Retry Request Id Preserved | ✅ | 5018ms |
| Partial Batch Handling.Respects Retry After On Partial | ✅ | 5020ms |
| Partial Batch Handling.Unknown Result Treated As Terminal | ✅ | 3021ms |
| Partial Batch Handling.Mixed Ok Drop Limited No Retry | ✅ | 3027ms |
| Compression.Sends Gzip Content Encoding | ✅ | 18ms |
| Compression.No Content Encoding When Disabled | ✅ | 15ms |
| Compression.Compressed Body Is Decompressible | ✅ | 12ms |
| Error Handling.Does Not Retry On Unknown 4Xx | ✅ | 4015ms |
| Event Options.Cookieless Mode Override | ✅ | 16ms |
| Event Options.Disable Skew Correction Override | ✅ | 14ms |
| Event Options.Process Person Profile Override | ✅ | 14ms |
| Event Options.Product Tour Id Override | ✅ | 15ms |
| Event Options.Unset Options Omitted | ✅ | 23ms |
| Event Options.Options Override In Batch | ✅ | 24ms |
| Event Options.Unknown Option Passes Through | ✅ | 15ms |
| Event Options.Option Value Is Not Converted | ✅ | 16ms |
| Event Options.Lenient Bool Option Values Are Not Converted | ✅ | 18ms |
| Event Options.Option Wins Over Legacy Property | ✅ | 14ms |
| Event Options.Legacy Property Fills Unset Option | ✅ | 14ms |
| Event Options.Null Option Falls Back To Legacy Property | ✅ | 14ms |
| Geoip And Historical Migration.Geoip Disable Injected Into Properties | ❌ | 16ms |
| Geoip And Historical Migration.Historical Migration Absent By Default | ✅ | 15ms |
Failures
event_format.capture_ai_keeps_supplied_uuid
Expected last action result '0198c0de-0000-7000-8000-000000000abc', got '01a18303-256b-7bf7-bb47-3b26e578bd65'
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 | ✅ | 33ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 19ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 18ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 18ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 21ms |
| Request Payload.Groups Round Trip | ✅ | 20ms |
| Request Payload.Groups Default To Empty Object | ✅ | 19ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 16ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 16ms |
| 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 | ✅ | 15ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 22ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 22ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 323ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 319ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 18ms |
822fed2 to
d44bc39
Compare
b373a1a to
75c4f81
Compare
marandaneto
left a comment
There was a problem hiding this comment.
The code looks good; no qualifying findings. Verdict: correct.
marandaneto
left a comment
There was a problem hiding this comment.
The security follow-up found a reproduced credential disclosure. This supersedes my earlier clean-review conclusion.
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.
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.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
1633e92 to
5346aa9
Compare
💡 Motivation and Context
Stacked on #884.
We agreed that SDKs should drop events only when they can never be delivered (event too large, duplicate uuid) or when the buffer is full. A 404 usually means a misconfigured proxy, and a 429 now comes only from an overloaded Envoy. Neither should drop events.
This adds 404 and 429 to the capture v1 retry set for analytics and AI events. They use the same backoff,
Retry-Afterhandling and retry budget as 408 and 5xx. 3xx stays terminal. Reference: PostHog/posthog-go#395.isCaptureV1RetriableStatusCodenow covers 404 and 429. The AI endpoint, the queue and the legacy cached-events path all use it.💚 How did you test it?
./gradlew :posthog:test --tests com.posthog.PostHogCaptureAiTest --tests com.posthog.internal.EndpointSpecTest --tests com.posthog.internal.PostHogQueueTest --tests com.posthog.internal.PostHogCaptureV1Test --tests com.posthog.internal.PostHogQueueDurabilityTest --tests com.posthog.internal.PostHogSendCachedLegacyEventsIntegrationTest: 125 tests passed../gradlew spotlessCheckpassed.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file (updated the existing unreleased v4 major changeset)🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Claude Code applied the policy change from posthog-go#395 to the capture v1 predicate, tests and docs. The unreleased v4 changeset already describes the retry behavior, so this PR updates it rather than adding a new changeset. Logs, replay and push-subscription retry policies are unchanged.
🤖 Generated with Claude Code