Skip to content

fix(server-utils): Mark AI spans as cancelled when the caller aborts - #26297

Merged
andreiborza merged 8 commits into
developfrom
fix/ai-caller-abort-status
Nov 13, 2026
Merged

andreiborza merged 8 commits into
developfrom
fix/ai-caller-abort-status

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Why: If the caller aborts an OpenAI, Anthropic or Google GenAI call before the response arrives, or aborts a non-streaming call, the span is recorded as internal_error. The same abort mid-stream is already cancelled on Google GenAI and Bedrock. These calls now record cancelled on both the client-wrapper path and the Orchestrion path. We check the signal, not the error name, because SDK timeouts also throw an AbortError. A caller's own AbortSignal.timeout() also stays an error: it's a timeout, and under span streaming cancelled is sent as ok.

Anthropic's messages.stream() follows the same rule. stream.aborted is set for any abort of the caller's signal and doesn't say why, so its 'end' listener now also reads the caller's signal. An abort with no signal (stream.abort(), or breaking out of the loop) stays cancelled. This also removes a duplicate 'end' listener on Anthropic's MessageStream, left over from two PRs merging into the same function.

How to review: Start with getErrorStatus / markCancelledIfAborted in ai/core/utils.ts. The provider changes are call sites. The new OpenAI and Anthropic scenario-abort integration tests fail on develop and pass here.

Refs #26130. This covers the pre-stream item. Checking the signal after the loop for OpenAI/Anthropic mid-stream, and cancel() on Mistral and Workers AI, are still open there.

via Rola Abuhasna.

--

View Session [Sentry]

RulaKhaled and others added 2 commits November 11, 2026 14:52
A caller's abort before the response arrived, or on a non-streaming call, was
recorded as an error on the OpenAI, Anthropic and Google GenAI spans. It is now
`cancelled`, matching what Google GenAI and Bedrock already record for an abort
mid-stream. A caller's own `AbortSignal.timeout()` stays an error.

Also removes the duplicate `'end'` listener on Anthropic's MessageStream.

Refs #26130

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generic already declares `aborted`, so the cast left over from the duplicate
`'end'` listener isn't needed.

Co-Authored-By: Rola Abuhasna <rola.abuhasna@sentry.io>
@RulaKhaled RulaKhaled self-assigned this Nov 11, 2026
@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

size-limit report 📦

Compared: a9ef3cb (PR test merge using develop at c7d73e4)

Against: c7d73e4 (develop) · base run #66026

✅ Baseline matches: this diff contains only this PR's changes.

Path Size % Change Change
@sentry/browser 30.1 kB - -
@sentry/browser - with treeshaking flags 28.3 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 28.2 kB - -
@sentry/browser (incl. Tracing) 52.28 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 52.29 kB - -
@sentry/browser (incl. Tracing, Profiling) 55.25 kB - -
@sentry/browser (incl. Tracing, Replay) 92.82 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 81.6 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 97.21 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 110.6 kB - -
@sentry/browser (incl. Feedback) 47.73 kB - -
@sentry/browser (incl. sendFeedback) 30.69 kB - -
@sentry/browser (incl. FeedbackAsync) 35.69 kB - -
@sentry/browser (incl. Metrics) 31.43 kB - -
@sentry/browser (incl. Logs) 31.71 kB - -
@sentry/browser (incl. Metrics & Logs) 32.37 kB - -
@sentry/react 32.11 kB - -
@sentry/react (incl. Tracing) 54.95 kB - -
@sentry/vue 38.3 kB - -
@sentry/vue (incl. Tracing) 55.24 kB - -
@sentry/svelte 30.12 kB - -
@sentry/remix (Remix 3 client bundle) 58.06 kB - -
CDN Bundle 31.87 kB - -
CDN Bundle (incl. Tracing) 52.7 kB - -
CDN Bundle (incl. Logs, Metrics) 34.67 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 55.26 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 75.7 kB - -
CDN Bundle (incl. Tracing, Replay) 90.72 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 93.33 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 96.09 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 98.69 kB - -
CDN Bundle - uncompressed 92.98 kB - -
CDN Bundle (incl. Tracing) - uncompressed 155.45 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 101.33 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 163.08 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 231.43 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 275.6 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 283.23 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 287.39 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 295.01 kB - -
@sentry/nextjs (client) 58.34 kB - -
@sentry/sveltekit (client) 53.46 kB - -
@sentry/core/server 44.57 kB - -
@sentry/core/browser 13.99 kB - -
@sentry/node 150.32 kB +0.09% +128 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 83.3 kB - -
@sentry/node - without tracing 88.47 kB -0.01% -8 B 🔽
@sentry/node - without channel injection 139.28 kB +0.11% +147 B 🔺
@sentry/aws-serverless 98.39 kB +0.03% +25 B 🔺
@sentry/cloudflare (withSentry) - minified 204.55 kB - -
@sentry/cloudflare (withSentry) 474.76 kB - -
@sentry/nextjs/cloudflare (withSentry) - minified 229.86 kB - -

View base workflow run

@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Optional E2E tests

Some optional checks were not successful. They do not block the merge. 4 not successful, 54 successful. See the run.

Check Status
❌ CI: Optional E2E Tests / E2E node-eve Test (optional) Failing after 2m Details
❌ CI: Optional E2E Tests / E2E nextjs-16-cacheComponents (cloudflare) Test (optional) Failing after 10m Details
❌ CI: Optional E2E Tests / E2E node-eve (latest) Test (optional) Failing after 2m Details
❌ CI: Optional E2E Tests / E2E node-eve (orchestrion) Test (optional) Failing after 2m Details
Successful checks
Check Status
✅ CI: Optional E2E Tests / E2E astro-7-static Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E cloudflare-flue-send-to-sentry Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E cloudflare-workers-send-to-sentry Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E cloudflare-pi-durable (latest) Test (optional) Successful in 51s Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries Test (optional) Successful in 59s Details
✅ CI: Optional E2E Tests / E2E cloudflare-think Test (optional) Successful in 58s Details
✅ CI: Optional E2E Tests / E2E debug-id-sourcemaps Test (optional) Successful in 49s Details
✅ CI: Optional E2E Tests / E2E nestjs-12 (cloudflare) Test (optional) Successful in 33s Details
✅ CI: Optional E2E Tests / E2E cloudflare-think (ai v6) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries (cloudflare) Test (optional) Successful in 43s Details
✅ CI: Optional E2E Tests / E2E nextjs-15 (turbo) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nestjs-12 (deno) Test (optional) Successful in 53s Details
✅ CI: Optional E2E Tests / E2E deno (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nestjs-12 (bun) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nestjs-12 (latest) Test (optional) Successful in 59s Details
✅ CI: Optional E2E Tests / E2E nextjs-14 (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-15 (latest) Test (optional) Successful in 3m Details
✅ CI: Optional E2E Tests / E2E nextjs-16-tunnel (cloudflare) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (vinext, bun) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (vinext) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-orpc Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-express-mcp-v1 Test (optional) Successful in 52s Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (cloudflare, latest) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-express-send-to-sentry Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-express-mcp-v2 Test (optional) Successful in 45s Details
✅ CI: Optional E2E Tests / E2E node-flue Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-fastify-6 (tracer provider) Test (optional) Successful in 54s Details
✅ CI: Optional E2E Tests / E2E node-express-send-to-sentry-static Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-openai-decisions Test (optional) Successful in 43s Details
✅ CI: Optional E2E Tests / E2E node-fastify-6 Test (optional) Successful in 30s Details
✅ CI: Optional E2E Tests / E2E node-mastra (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-openai-decisions (latest) Test (optional) Successful in 28s Details
✅ CI: Optional E2E Tests / E2E node-flue (latest) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-mastra Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-pi-durable (latest) Test (optional) Successful in 33s Details
✅ CI: Optional E2E Tests / E2E nuxt-5 Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-pi-durable Test (optional) Successful in 46s Details
✅ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps Test (optional) Successful in 39s Details
✅ CI: Optional E2E Tests / E2E nuxt-5 (tunnel-dev) Test (optional) Successful in 49s Details
✅ CI: Optional E2E Tests / E2E nuxt-5 (kv-cache) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps (static / nuxt generate) Test (optional) Successful in 33s Details
✅ CI: Optional E2E Tests / E2E supabase-nextjs Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps (client source maps kept) Test (optional) Successful in 44s Details
✅ CI: Optional E2E Tests / E2E react-router-7-framework-instrumentation Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nuxt-5-cloudflare Test (optional) Successful in 41s Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react-sourcemaps Test (optional) Successful in 38s Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E solidstart-2 Test (optional) Successful in 51s Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react-cloudflare Test (optional) Successful in 50s Details
✅ CI: Optional E2E Tests / E2E cloudflare-pi-durable Test (optional) Successful in 53s Details
✅ CI: Optional E2E Tests / E2E astro-7 Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E cloudflare-agent Test (optional) Successful in 56s Details
✅ CI: Optional E2E Tests / E2E cloudflare-mastra Test (optional) Successful in 47s Details

…tream() as an error

`stream.aborted` is set for any abort of the caller's signal, so a timeout was
recorded as `cancelled`. The 'end' listener now reads the caller's signal, the
same check `messages.create()` uses. An abort with no signal, like
`stream.abort()`, stays `cancelled`.

Co-Authored-By: Rola Abuhasna <rola.abuhasna@sentry.io>
@RulaKhaled
RulaKhaled marked this pull request as ready for review November 12, 2026 12:07
@RulaKhaled
RulaKhaled requested a review from a team November 12, 2026 12:07
@RulaKhaled
RulaKhaled requested a review from a team as a code owner November 12, 2026 12:07
…t-status

# Conflicts:
#	packages/server-utils/src/integrations/anthropic.ts
#	packages/server-utils/src/integrations/aws-sdk/services/bedrock-runtime.ts
#	packages/server-utils/src/integrations/openai.ts

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit bb996a5. Configure here.

A thread stream sends each `create()` with its own controller signal, which
doesn't carry the reason the caller's signal fired with. Keep the caller's
signal from `_start()` so an `AbortSignal.timeout()` on a thread stream is an
error, like on `messages.stream()`.
…t-status

# Conflicts:
#	packages/server-utils/src/integrations/aws-sdk/services/bedrock-runtime.ts
@andreiborza
andreiborza merged commit 6a4315b into develop Nov 13, 2026
464 of 478 checks passed
@andreiborza
andreiborza deleted the fix/ai-caller-abort-status branch November 13, 2026 16:18
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.

2 participants