A fetch() span that already got a 200 response ends up with status: error when the caller cancels the body stream (reader.cancel()) or calls AbortController.abort() after the headers arrived. undici reports that as undici:request:error with an AbortError instead of undici:request:trailers, and onError in the undici instrumentation sets SPAN_STATUS_ERROR for every error.
This ports the check from upstream OpenTelemetry (open-telemetry/opentelemetry-js-contrib#3488, "do not record aborted requests as errors"): if the error is an AbortError, or a DOMException with ABORT_ERR, the span is ended without touching its status. Other errors (connection reset, TypeError: fetch failed, ...) still set the error status as before. I updated the NOTE comment above onError to say so.
This matters for clients that read a stream and then close it as part of normal operation. For example the MCP TypeScript SDK closes its event stream on client close, so every healthy session showed http.client error spans.
Repro
import*asSentryfrom"@sentry/node";import{createServer}from"node:http";Sentry.init({dsn: "http://key@127.0.0.1:9/1",tracesSampleRate: 1,beforeSendSpan(s){console.log(s.name??s.description,s.status);returns;}});constsrv=createServer((req,res)=>{if(req.url==="/stream"){res.writeHead(200,{"content-type": "text/event-stream"});res.write("data: 1\n\n");return;}res.writeHead(200);res.end("ok");});awaitnewPromise((r)=>srv.listen(0,"127.0.0.1",r));constbase=`http://127.0.0.1:${srv.address().port}`;awaitSentry.startSpan({name: "root"},async()=>{await(awaitfetch(base+"/plain")).text();// okconstr=(awaitfetch(base+"/stream")).body.getReader();awaitr.read();awaitr.cancel();// error, although the response was 200});awaitSentry.flush(2000);srv.closeAllConnections();srv.close();
Before (11.4.0): the fully read request is ok, the cancelled one is error with http.response.status_code 200. Same with AbortController.abort() after the headers arrive.
After (this branch, built with yarn build:dev:filter @sentry/node): both are ok.
Tests
Added unit tests to packages/node/test/integrations/undici.test.ts that publish the undici:request:create, undici:request:headers (200) and undici:request:error diagnostics channel messages:
abort errors (AbortError, DOMException named AbortError, DOMException with only the ABORT_ERR code) end the span without an error status
control: a TypeError and an ECONNRESET error still set the error status
cd packages/node && yarn vitest run test/integrations/undici.test.ts: without the fix 3 of 10 fail (the abort cases), with the fix 10 of 10 pass. oxfmt --check and oxlint --type-aware are clean on both files.
If you've added code that should be tested, please add tests.
Ensure your code lints and the test suite passes (yarn lint) & (yarn test). (Ran the changed test file, oxfmt and oxlint on the changed files; not the full suite.)
Link an issue if there is one related to your pull request. (There is none; this PR has the repro.)
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
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.
A fetch() span that already got a 200 response ends up with status: error when the caller cancels the body stream (reader.cancel()) or calls AbortController.abort() after the headers arrived. undici reports that as undici:request:error with an AbortError instead of undici:request:trailers, and onError in the undici instrumentation sets SPAN_STATUS_ERROR for every error.
This ports the check from upstream OpenTelemetry (open-telemetry/opentelemetry-js-contrib#3488, "do not record aborted requests as errors"): if the error is an AbortError, or a DOMException with ABORT_ERR, the span is ended without touching its status. Other errors (connection reset, TypeError: fetch failed, ...) still set the error status as before. I updated the NOTE comment above onError to say so.
This matters for clients that read a stream and then close it as part of normal operation. For example the MCP TypeScript SDK closes its event stream on client close, so every healthy session showed http.client error spans.
Repro
Before (11.4.0): the fully read request is ok, the cancelled one is error with http.response.status_code 200. Same with AbortController.abort() after the headers arrive.
After (this branch, built with yarn build:dev:filter @sentry/node): both are ok.
Tests
Added unit tests to packages/node/test/integrations/undici.test.ts that publish the undici:request:create, undici:request:headers (200) and undici:request:error diagnostics channel messages:
cd packages/node && yarn vitest run test/integrations/undici.test.ts: without the fix 3 of 10 fail (the abort cases), with the fix 10 of 10 pass. oxfmt --check and oxlint --type-aware are clean on both files.