Debugging Node.js 20 Upgrade Issues: Interaction Between Request, Sentry, and the HTTP Module
When Tubi upgraded from Node.js 14 to 20, server‑side requests began returning 404 errors, leading to a deep investigation that uncovered how the Request library and Sentry SDK altered HTTP module arguments, causing the built‑in URL detection to misclassify plain objects as URLs.
At Tubi we upgraded our Node.js runtime from version 14.x to 20.x to benefit from performance improvements and new features. The upgrade broke server‑side requests, which started failing with 404 responses.
Our first suspect was the deprecated request library. Reproducing the issue with request.get('https://foo.bar/get') on Node.js 20 returned various errors, including 404. Enabling NODE_DEBUG=http,net and the trace flags -trace-event-categories node.http,node.net.native revealed that the HTTP module received a path value of / instead of the expected /get .
When the same code was run on Node.js 14, the path was correctly set to /get , explaining the 404s on the newer runtime.
We created two minimal reproduction cases—one using the native http module and another using the request library—but neither reproduced the anomaly, prompting us to suspect an issue in our codebase.
Searching the request repository’s issues uncovered a similar problem linked to the Sentry v2 SDK. Introducing Sentry into the Node.js 20 environment reproduced the failure, confirming that the combination of request , Sentry, and Node.js 20 triggered the bug.
To inspect the real arguments passed to the built‑in http(s) module, we ran Node.js with -expose-internals and -r internal/test/binding , allowing us to copy and modify the module source:
// const https = require('https'); // const https = require('./path/to/local/https');By printing logs and adding breakpoints we discovered that the http(s) module accepts either a WHATWG URL object or a plain object. The request library passes a plain object, but the module mistakenly treats it as a URL because the object contains href and protocol properties added by Sentry.
The module’s isURL function determines URL objects by checking for href , protocol , and self.auth === undefined . Both request and Sentry inject these properties, causing the false positive and resulting in an empty path after the conversion:
function isURL(self) { return Boolean(self?.href && self.protocol && self.auth === undefined); }The conversion logic in urlToHttpOptions builds the path from pathname and search :
function urlToHttpOptions(url) { const { pathname, search } = url; const options = { /* ... */ path: `${pathname || ''}${search || ''}` }; return options; }Because the injected object lacks pathname and search , the resulting path is empty, leading to the 404 error.
We opened a discussion on the Node.js GitHub issue tracker and submitted a pull request that adjusts the URL detection logic. The fix will be released in Node.js v20.6.0 and back‑ported to v18.18.0.
Key takeaways:
Debug complex upgrade issues by isolating layers and reproducing minimal cases.
Avoid using deprecated libraries like request that may introduce hidden side‑effects.
Do not patch native modules; instead, rely on upstream fixes.
When URL detection is unreliable, checking for path === undefined can differentiate plain objects from WHATWG URL instances.
For more details, see the linked resources and the original technical blog.
Bitu Technology
Bitu Technology is the registered company of Tubi's China team. We are engineers passionate about leveraging advanced technology to improve lives, and we hope to use this channel to connect and advance together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.