Skip to content

createDevServer starts on a port that another server holds on 127.0.0.1 #460

Open

Description

@DBAAZzz

Summary

If another server listens on 127.0.0.1:<port>, createDevServer still starts on the same port and reports no error. With the default host localhost, devframe binds only [::1]:<port>. Requests to 127.0.0.1:<port> then go to the other server.

The cause is in get-port-please, which devframe bundles. getPort({ port, host }) tries to bind only the given host. 127.0.0.1 and ::1 can each hold the same port, so the port looks free. This is unjs/get-port-please#121, which is still open.

packages/devframe/src/node/instance-shell.ts already notes that getPort can "disagree across the v4/v6 duals of localhost". The retry there runs only when the bind fails with EADDRINUSE. In this case the bind succeeds, so the retry does not run.

Versions

  • devframe 1.2.6 (main at 0e4a49c7), get-port-please 3.2.0 (the latest release)
  • Linux: Node 22.23 in Docker node:22-bookworm, with IPv6 on the loopback interface
  • macOS 15: Node 22.22

Reproduction

import { createServer } from 'node:net'
import { defineDevframe } from 'devframe'
import { createDevServer } from 'devframe/adapters/dev'

const port = 19876

// Another server holds the port on 127.0.0.1 only.
const other = createServer()
await new Promise(resolve => other.listen(port, '127.0.0.1', resolve))

const devframe = defineDevframe({ id: 'repro', name: 'Repro', cli: { port }, setup() {} })
const server = await createDevServer(devframe, { openBrowser: false })

console.log(server.port) // Expected: a port other than 19876

await server.close()
other.close()

The script prints 19876 on Linux and on macOS.

Results with another server on 127.0.0.1:<port>:

Platform host Conflict found devframe binds
Linux, IPv6 on localhost or not set No [::1]:<port>
Linux, IPv6 on 0.0.0.0 Yes another port
Linux, IPv6 off any Yes another port
macOS localhost or not set No [::1]:<port>
macOS 0.0.0.0 No *:<port>

When the conflict is not found, http://127.0.0.1:<port> reaches the other server. On Linux, Node fetch('http://localhost:<port>') also reaches the other server. With host: '0.0.0.0' on macOS, localhost and 127.0.0.1 both reach the other server.

Proposed fix

I have a fix with tests, and I can open a PR:

  1. In get-port-please, getPort also tries 127.0.0.1, ::1, 0.0.0.0 and :: when the host is localhost, a loopback address, or a wildcard address. The port is in use if one of these binds fails with EADDRINUSE or EACCES. Other errors, such as EADDRNOTAVAIL when IPv6 is off, mean that the address does not exist. They do not count as "in use".
  2. devframe bundles get-port-please, so it carries the same change as a pnpm patch (patches/get-port-please@3.2.0.patch). The fix then reaches every caller of devframe/utils/get-port, for example Vite DevTools. You can remove the patch when upstream releases a fix.
  3. A regression test in packages/devframe/src/adapters/__tests__/dev.test.ts. It fails on main and passes with the patch, on Linux (IPv6 on) and on macOS.

I will also send the same change upstream for unjs/get-port-please#121. If you want a different shape, tell me and I will change the PR. One option is a small wrapper in devframe/utils/get-port instead of a patch.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions