Pull Request Overview
- Opened on October 12, 2026
- Status Open
- Commit count 1 with first commit October 12, 2026
Total Delta
Open Days
Test Delta
How long has this pull request spent in each phase of its lifecycle?
| Fraction of total time | Business days | Phase |
|---|---|---|
| N/A | 21.0 days | Issue creation to work beginning |
|
|
0.0 days | Authoring 1 commit before pull request opened for review |
|
|
0.0 days | Awaiting first review |
|
|
24.0 days | Revising work with 0 commits in response to 0 reviews that left 1 comment |
Total time for pull request still awaiting merge (longer than repo's target): 24.0 business days
[DevTools] Bind standalone server to the configured host
Summary
Fixes #37624. startServer(port, host) never passed host to httpServer.listen(), so the standalone server always listened on all interfaces, even with HOST=127.0.0.1.
-
hostis now passed tolisten(). Without a host, the server still listens on all interfaces and clients are told to connect tolocalhost, as before. React Native on physical devices and the app's LAN-IP snippet rely on this. - The Electron app passes
HOSTonly when it's set. Defaulting it tolocalhostwould bind::1only on Node 17+, breaking IPv4 clients such as the Android emulator (10.0.2.2).
Behavior change: an explicit HOST/host now restricts where the server listens. To only change the host clients connect to, use REACT_DEVTOOLS_CLIENT_HOST/clientOptions.host. The READMEs are updated.
Known issue, to be fixed separately: if binding fails (for example, a HOST that doesn't resolve), the existing retry-on-error path schedules two retries per failure, so retries double every second.
How did you test this change?
- New
standalone-test.jschecks the address the server listens on. Before this change it gets::instead of127.0.0.1. - Built the Electron app from this branch and ran it with
HOSTunset. It connected tofixtures/devtools/standalonevia bothlocalhostand the LAN-IP snippet. It also connected to a React Native app on the iOS Simulator via bothlocalhostand the LAN IP. - With
127.0.0.1, connecting through the LAN IP is now refused; before this change it succeeded.
Website or app
React DevTools standalone package: https://github.com/react/react/tree/main/packages/react-devtools
Repro steps
[email protected] passes its configured host to
react-devtools-core/standalone.startServer(port, host). The default host is
documented as localhost, but the supplied value is not applied to the HTTP
listening socket.
- Create an empty directory and install the current packages used by this reproduction:
npm init -y
npm install [email protected] [email protected]
- Save the following as
bind-check.cjs:
'use strict';
const { JSDOM, VirtualConsole } = require('jsdom');
const net = require('node:net');
const os = require('node:os');
const dom = new JSDOM('<!doctype html><body></body>', {
pretendToBeVisual: true,
url: 'http://localhost',
virtualConsole: new VirtualConsole(),
});
dom.window.ResizeObserver = class {
observe() {}
unobserve() {}
disconnect() {}
};
for (const key of [
'window',
'document',
'navigator',
'HTMLElement',
'Element',
'Node',
'localStorage',
'sessionStorage',
]) {
global[key] = key === 'window'
? dom.window
: key === 'document'
? dom.window.document
: dom.window[key];
}
global.getComputedStyle = dom.window.getComputedStyle.bind(dom.window);
global.requestAnimationFrame = dom.window.requestAnimationFrame.bind(dom.window);
global.cancelAnimationFrame = dom.window.cancelAnimationFrame.bind(dom.window);
const nonLoopback = Object.values(os.networkInterfaces())
.flat()
.filter(Boolean)
.find(entry => entry.family === 'IPv4' && !entry.internal);
if (!nonLoopback) throw new Error('No non-loopback IPv4 address found');
const DevTools = require('react-devtools-core/standalone').default;
DevTools.setContentDOMNode(document.body);
const port = 18098;
const requestedHost = '127.0.0.1';
const server = DevTools.startServer(port, requestedHost);
function finish(code) {
server.close();
setTimeout(() => process.exit(code), 50);
}
setTimeout(() => {
const socket = net.connect(port, nonLoopback.address);
socket.on('connect', () => {
console.log(JSON.stringify({
requestedHost,
connectedVia: nonLoopback.address,
}));
socket.destroy();
finish(0);
});
socket.on('error', error => {
console.error(error);
finish(1);
});
}, 100);
setTimeout(() => finish(2), 5000);
- Run:
node bind-check.cjs
- Although
startServer()was explicitly called with127.0.0.1, the connection through the same machine's non-loopback address succeeds:
{"requestedHost":"127.0.0.1","connectedVia":"<non-loopback IPv4>"}
The test makes only a local connection to the same machine.
Expected: passing 127.0.0.1 or localhost restricts the listening socket
to that host, as described by the API and standalone documentation.
Actual: the implementation calls httpServer.listen(port) without the
supplied host, so Node listens on an unspecified address.
A direct fix would be to forward the argument:
httpServer.listen(port, host, callback)
Environment used: macOS, Node.js 20.19.5.
How often does this bug happen?
Every time
DevTools package (automated)
No response
DevTools version (automated)
No response
Error message (automated)
No response
Error call stack (automated)
Error component stack (automated)
GitHub query string (automated)
Comments Threads Pending Resolution
Resolved Comment Threads
No resolved comments have been left on this PR.