Skip to content

req.signal.aborted becomes true after request body parse / stream close in Node 24.18.0 #64390

Description

@dnorrstrand

Version

v24.18.0

Platform

Darwin Deniss-MacBook-Pro.local 25.5.0 Darwin Kernel Version 25.5.0: Mon Apr 27 20:41:19 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T8122 arm64

Subsystem

No response

What steps will reproduce the bug?

In Node 24.18.0, IncomingMessage.prototype.signal.aborted becomes true after the incoming message body has finished parsing and the request stream closes, even though req.aborted === false. This makes req.signal.aborted unreliable for detecting actual client aborts.

Context

  • Node version: v24.18.0
  • Environment: local dev, no reverse proxy
  • Framework: Express + body-parser
  • Route: PATCH /tables/1
  • Comparison: a separate GET route does not show the issue

Suggested reproduction steps

  1. Start an Express server on Node 24.18.0
  2. Add middleware logging:
  • req.aborted
  • req.complete
  • req.signal.aborted
  • req.socket.destroyed
  1. Send a PATCH request with a JSON body
  2. Observe:
  • early middleware logs signalAborted: false
  • later the same request logs signal.aborted: true
  • req.close fires before signal.aborted becomes true

Code to reproduce:


// repro.js
import express from "express";
import bodyParser from "body-parser";

const app = express();
app.use(bodyParser.json());

app.use((req, res, next) => {
	console.log("early middleware:", {
		url: req.originalUrl,
		method: req.method,
		aborted: req.aborted,
		complete: req.complete,
		signalAborted: req.signal.aborted
	});

	req.once("close", () => {
		console.log("req close event", req.originalUrl, {
			aborted: req.aborted,
			signalAborted: req.signal.aborted
		});
	});

	next();
});

app.patch("/tables/1", async (req, res) => {
	console.log("route start:", {
		aborted: req.aborted,
		signalAborted: req.signal.aborted
	});

	await new Promise((resolve) => setTimeout(resolve, 200));

	console.log("route before res send", {
		aborted: req.aborted,
		signalAborted: req.signal.aborted
	});

	res.json({ ok: true });
});

app.listen(3009, () => {
	console.log("listening on 3009");
});

How often does it reproduce? Is there a required condition?

Reproduces with PATCH requests that have a JSON body.

What is the expected behavior? Why is that the expected behavior?

req.signal.aborted should only become true when the client aborts the request (transport-level cancellation before or during request processing), not when the incoming message handling completes normally or when body parsing ends.

What do you see instead?

For a normal PATCH request with a body:

  • early request logs show signalAborted: false
  • request body parsing completes successfully
  • req.close triggers
  • later, req.signal.aborted becomes true
  • req.aborted remains false

Key evidence

  • req.close event in route handler is logged before the route observes signal.aborted === true
  • req.aborted remains false
  • normal cURL request reproduces it
  • the same request type in a different route does not reproduce it

This means the new signal.aborted property is firing on request stream completion/closure rather than on genuine abort.

Additional information

Why this is a bug

The new req.signal API is supposed to expose request cancellation state. If it becomes true after normal request completion or stream close, it is effectively useless for distinguishing real client aborts. This appears to be the Node HTTP internals abort signal being fired when the request handling completes, rather than when the client actually aborts. That behavior breaks the use case of req.signal.aborted for client abort detection.

Metadata

Metadata

Assignees

No one assigned

    Labels

    httpIssues or PRs related to the http subsystem.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions