Skip to content

fix(ai): mark cancelled async LLM calls as errors - #1065

Merged
marandaneto merged 2 commits into
mainfrom
fix/ai-async-cancellation-1064
Oct 15, 2026
Merged

marandaneto merged 2 commits into
mainfrom
fix/ai-async-cancellation-1064

Conversation

@marandaneto

@marandaneto marandaneto commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

💡 Motivation and Context

Fixes #1064.

Cancelled async LLM calls currently emit $ai_generation with HTTP status 200 and no error fields. This counts cancelled requests as successful generations and includes their timeout latency in successful-call metrics.

The shared non-streaming helper now handles asyncio.CancelledError explicitly. It captures status 0, $ai_is_error: true, and $ai_error: "cancelled", then re-raises the original cancellation. Ordinary provider errors keep their existing behavior. Streaming and synchronous calls are unchanged.

The published capture-ai SDK spec does not define cancellation classification. This change does not alter the public API or the spec's capture contract.

💚 How did you test it?

  • Before the fix, all four cancellation regression cases failed because the captured status was 200. The provider-error control passed.
  • Added task cancellation tests for the shared helper's Gemini, OpenAI, and Anthropic paths. They check that one error event is captured, unknown token counts remain absent, and the task stays cancelled. The assertion does not depend on cancellation-message propagation, which changed in Python 3.11.
  • Added a timeout regression test through the public Gemini async wrapper. It checks that wait_for still raises TimeoutError and the generation is marked as an error.
  • AI test suite with live-provider credentials unset: 946 passed, 8 skipped, 10 integration tests deselected, and 14 subtests passed.
  • Repository-wide Ruff formatting and lint checks, mypy with baseline filtering, import warning check, and public API snapshot check passed.
  • Python 3.10 focused AI tests: 31 passed and 2 integration tests deselected. Removing the cancellation handler still makes all three shared-helper cancellation cases fail on the incorrect HTTP 200 status.
  • MCP v2 suite with FastMCP 4.0.11: 498 passed and 22 skipped. Removed two assertions that assumed FastMCP could not resolve middleware-injected tools directly. The HTTP argument-preservation and analytics assertions remain. Disabling instrumentation makes all seven affected HTTP cases fail.
  • Diff-scoped test audit and cleanup found no issues. Autoreview of commit cef2f8038f21bfefdf2b0f8e8691cad24e623849 against origin/main reported no actionable findings.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed. No documentation changes are needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran sampo add to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Implemented with Pi using GitHub CLI, uv/pytest, Ruff, mypy, Sampo, and autoreview. The investigation and fix used the same isolated worktree. The implementation catches only asyncio.CancelledError, rather than broadening exception handling to all BaseException subclasses. Human review is required.

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

posthog-python Compliance Report

Date: 2026-10-15T16:31:38.103205+00:00
Duration: 259227ms

✅ All Tests Passed!

121/121 tests passed


Capture_V1 Tests

✅ 95/95 tests passed

View Details
Test Status Duration
Endpoint And Method.Targets V1 Endpoint ✅ 514ms
Endpoint And Method.Does Not Use Legacy Endpoints ✅ 508ms
Required Headers.Has Authorization Bearer Header ✅ 508ms
Required Headers.Has Content Type Json ✅ 509ms
Required Headers.Has Posthog Sdk Info Format ✅ 508ms
Required Headers.Has Posthog Attempt Header ✅ 508ms
Required Headers.Has Posthog Request Id ✅ 508ms
Required Headers.Has Posthog Request Timestamp ✅ 508ms
Required Headers.Has User Agent ✅ 508ms
Body Format.Body Has Created At And Batch ✅ 508ms
Body Format.No Api Key In Body ✅ 508ms
Body Format.No Sent At In Body ✅ 508ms
Event Format.Event Has Required Root Fields ✅ 508ms
Event Format.Event Uuid Is Valid ✅ 509ms
Event Format.Event Timestamp Is Rfc3339 ✅ 507ms
Event Format.Non Utc Event Timestamp Is Converted To Utc ✅ 512ms
Event Format.Distinct Id Is String ✅ 508ms
Event Format.Distinct Id At Root Not Properties ✅ 508ms
Event Format.Custom Properties Preserved ✅ 507ms
Event Format.Set Properties Preserved ✅ 509ms
Event Format.Set Once Properties Preserved ✅ 507ms
Event Format.Groups Properties Preserved ✅ 508ms
Event Format.Sdk Generates Uuid If Not Provided ✅ 508ms
Event Format.Event Has Required Root Fields Batch ✅ 510ms
Event Format.Event Uuid Is Valid Batch ✅ 510ms
Event Format.Event Timestamp Is Rfc3339 Batch ✅ 511ms
Event Format.Distinct Id Is String Batch ✅ 510ms
Event Format.Distinct Id At Root Not Properties Batch ✅ 510ms
Event Format.Custom Properties Preserved Batch ✅ 510ms
Event Format.Set Properties Preserved Batch ✅ 510ms
Event Format.Set Once Properties Preserved Batch ✅ 510ms
Event Format.Groups Properties Preserved Batch ✅ 509ms
Event Format.Sdk Generates Uuid If Not Provided Batch ✅ 510ms
Batch Behavior.Multiple Events In Single Batch ✅ 513ms
Batch Behavior.Batch Envelope Smoke ✅ 511ms
Batch Behavior.Flush With No Events Sends Nothing ✅ 506ms
Batch Behavior.Flush At Triggers Batch ✅ 1008ms
Batch Behavior.Created At Reflects Batch Creation Time ✅ 508ms
Deduplication.Generates Unique Uuids ✅ 513ms
Deduplication.Different Events Same Content Different Uuids ✅ 509ms
Deduplication.Preserves Uuid On Retry ✅ 6513ms
Deduplication.Preserves Timestamp On Retry ✅ 6512ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 6519ms
Deduplication.No Duplicate Events In Batch ✅ 514ms
Header Behavior On Retry.Attempt Header Starts At One ✅ 508ms
Header Behavior On Retry.Attempt Header Increments On Retry ✅ 13523ms
Header Behavior On Retry.Request Id Preserved On Retry ✅ 6513ms
Header Behavior On Retry.Different Requests Have Different Request Ids ✅ 3016ms
Header Behavior On Retry.Request Timestamp Changes On Retry ✅ 6513ms
Response Format Validation.Success Response Has Uuid Keyed Results ✅ 509ms
Response Format Validation.Success Response Has Ok For Each Event ✅ 511ms
Response Format Validation.Success No Retry After When All Ok ✅ 509ms
Response Format Validation.Success Retry After Present When Retry Events ✅ 1513ms
Response Format Validation.Success No Retry After When Drop Only ✅ 510ms
Response Format Validation.Response Echoes Request Id ✅ 508ms
Retry Behavior.Retries On 408 ✅ 6513ms
Retry Behavior.Retries On 500 ✅ 6516ms
Retry Behavior.Retries On 503 ✅ 8520ms
Retry Behavior.Retries On 504 ✅ 6513ms
Retry Behavior.Retryable Errors Have Retry After ✅ 3513ms
Retry Behavior.Respects Retry After On Retryable Error ✅ 11520ms
Retry Behavior.Does Not Retry On 400 ✅ 2512ms
Retry Behavior.Does Not Retry On 401 ✅ 2522ms
Retry Behavior.Does Not Retry On 402 ✅ 2512ms
Retry Behavior.Does Not Retry On 413 ✅ 2511ms
Retry Behavior.Does Not Retry On 415 ✅ 2510ms
Retry Behavior.Non Retryable Errors Have No Retry After ✅ 2510ms
Retry Behavior.Implements Backoff ✅ 22522ms
Retry Behavior.Max Retries Respected ✅ 22537ms
Partial Batch Handling.Handles 200 Full Success ✅ 2511ms
Partial Batch Handling.Handles 200 With All Ok ✅ 3511ms
Partial Batch Handling.Does Not Retry Dropped Events ✅ 3515ms
Partial Batch Handling.Does Not Retry Limited Events ✅ 3510ms
Partial Batch Handling.Prunes Ok Events On Partial Retry ✅ 6518ms
Partial Batch Handling.Prunes Dropped Events On Partial Retry ✅ 6516ms
Partial Batch Handling.Retries Only Retry Events From Partial ✅ 6520ms
Partial Batch Handling.Partial Retry Preserves Uuids ✅ 6518ms
Partial Batch Handling.Partial Retry Attempt Header Increments ✅ 6519ms
Partial Batch Handling.Partial Retry Request Id Preserved ✅ 6514ms
Partial Batch Handling.Respects Retry After On Partial ✅ 8518ms
Partial Batch Handling.Unknown Result Treated As Terminal ✅ 3514ms
Partial Batch Handling.Mixed Ok Drop Limited No Retry ✅ 3513ms
Compression.Sends Gzip Content Encoding ✅ 509ms
Compression.No Content Encoding When Disabled ✅ 508ms
Compression.Compressed Body Is Decompressible ✅ 509ms
Error Handling.Does Not Retry On Unknown 4Xx ✅ 2510ms
Event Options.Cookieless Mode Override ✅ 508ms
Event Options.Disable Skew Correction Override ✅ 508ms
Event Options.Process Person Profile Override ✅ 508ms
Event Options.Product Tour Id Override ✅ 507ms
Event Options.Unset Options Omitted ✅ 508ms
Event Options.Options Override In Batch ✅ 510ms
Geoip And Historical Migration.Geoip Disable Injected Into Properties ✅ 508ms
Geoip And Historical Migration.Historical Migration Set In Body ✅ 509ms
Geoip And Historical Migration.Historical Migration Absent By Default ✅ 508ms

Capture_Ai Tests

✅ 5/5 tests passed

View Details
Test Status Duration
Routing.Capture Ai Posts To Ai Endpoint ✅ 508ms
Routing.Capture Does Not Reroute Ai Named Events ✅ 508ms
Identity.Capture Ai Event Has Uuid ✅ 508ms
Identity.Capture Ai Keeps Supplied Uuid ✅ 507ms
Timestamp Format.Non Utc Event Timestamp Is Converted To Utc ✅ 508ms

Feature_Flags Tests

✅ 17/17 tests passed

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

Feature_Flags_Local_Evaluation Tests

✅ 4/4 tests passed

View Details
Test Status Duration
Versioned Boolean Matching.Matching Version Missing ✅ 47ms
Versioned Boolean Matching.Matching Version 1 ✅ 42ms
Versioned Boolean Matching.Matching Version 2 ✅ 43ms
Versioned Boolean Matching.Version Only Reload 1 2 1 2 Missing ✅ 23ms

@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 cef2f80: no findings.

This Standard review started from the posthog-review label. Comment @posthog review --deep for a Deep review, or @posthog help to see all commands.

@posthog posthog Bot removed the reviewhog label Oct 15, 2026
@marandaneto marandaneto added the stamphog Request stamphog review label Oct 15, 2026
@marandaneto
marandaneto merged commit fec41f2 into main Oct 15, 2026
69 of 71 checks passed
@marandaneto
marandaneto deleted the fix/ai-async-cancellation-1064 branch October 15, 2026 16:59

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved.

The telemetry write-path change has independent assurance from current-head reviews and focused regression coverage. No unresolved substantive concerns or other refusal grounds were found.

  • Author wrote 0% of the modified lines and has 61 merged PRs in these paths (familiarity MODERATE).
  • PostHog Review (Standard) reviewed the current head: no findings.
  • posthog[bot], turnipdabeets reviewed the current head.
Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✓ no deny categories matched
size ✓ 8L, 1F substantive, 129L/5F incl. docs/generated/snapshots — within ceiling
tier ✓ T1-agent / T1c-medium (129L, 5F, two-areas, fix)
stamphog 2.5.1 .stamphog/policy.yml @ cef2f80 · reviewed head cef2f80

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stamphog Request stamphog review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AI wrappers: cancelled async calls (asyncio.CancelledError) are captured as successful $ai_generation (HTTP 200, no error)

2 participants