Skip to content

npx hyperframes preview always fails on Crostini (ChromeOS Linux) + port scan race condition with wildcard addresses #309

@gigadeniga

Description

@gigadeniga

Describe the bug

Environment

  • OS: Crostini (Debian 12 "bookworm" — ChromeOS Linux container)
  • Kernel: 6.6.99-09128-g14e87a8a9b71
  • Node.js: v24.13.1
  • npm: 10.8.2
  • Hyperframes CLI: 0.4.3

Problem

npx hyperframes preview always fails on Crostini, regardless of which port is specified or whether all existing processes are killed first.


Steps to reproduce

  1. Install Hyperframes on a Crostini container
  2. Run npx hyperframes preview in any Hyperframes project directory

Output

┌  hyperframes preview
│
◇  Failed to start studio

  Ports 3002–3101 are all in use. Use --port to specify a different starting port.

Trying --port with any range gives the same result. Killing all node processes beforehand makes no difference.


Root cause

I found the issue in cli.js. The testPortOnAllHosts function checks 4 host bindings in parallel using Promise.all:

async function testPortOnAllHosts(port) {
  const hosts = ["127.0.0.1", "0.0.0.0", "::1", "::"];
  const results = await Promise.all(hosts.map((h) => isPortAvailableOnHost(port, h)));
  return results.every(Boolean);
}

isPortAvailableOnHost works by temporarily binding a TCP server to the port, then closing it:

function isPortAvailableOnHost(port, host) {
  return new Promise((resolve) => {
    const server = net.createServer();
    server.unref();
    server.on("error", (err) => {
      resolve(err.code !== "EADDRINUSE");
    });
    server.listen({ port, host }, () => {
      server.close(() => {
        resolve(true);
      });
    });
  });
}

The race condition: Promise.all fires all 4 binds at the same time. The 127.0.0.1 bind succeeds and its socket stays open while it waits for server.close(). Meanwhile, 0.0.0.0 (wildcard IPv4) and :: (wildcard IPv6) both attempt to bind — and they see EADDRINUSE because 0.0.0.0/:: are wildcard addresses that include 127.0.0.1. They are colliding with the first test's still-open socket.

testPortOnAllHosts returns false for every port. All 100 ports in the scan range appear to be in use. Preview always fails.


Reproducing the race in isolation

You can verify this on any machine:

const net = require('net');

const hosts = ['127.0.0.1', '0.0.0.0', '::1', '::'];

hosts.forEach(host => {
  const s = net.createServer();
  s.listen({ port: 7777, host }, () => {
    console.log(host + ': OK');
    s.close();
  });
  s.on('error', e => console.log(host + ': ' + e.code));
});

Output:

127.0.0.1: OK
0.0.0.0: EADDRINUSE
::1: OK
::: EADDRINUSE

0.0.0.0 and :: always return EADDRINUSE because 127.0.0.1's socket is still open when they try to bind. This makes every port in the scan appear to be taken.


Suggested fix

Test the hosts sequentially instead of in parallel, so each socket is fully closed before the next one opens:

async function testPortOnAllHosts(port) {
  const hosts = ["127.0.0.1", "0.0.0.0", "::1", "::"];
  for (const host of hosts) {
    const available = await isPortAvailableOnHost(port, host);
    if (!available) return false;
  }
  return true;
}

Alternatively, testing only 0.0.0.0 (covers all IPv4 including 127.0.0.1) and :: (covers all IPv6 including ::1) would be sufficient and avoids the redundancy that creates the race.


Workaround

npx vite --host 0.0.0.0 --port 5173

This serves the raw HTML files but without the studio timeline UI.

Link to reproduction

https://github.com/heygen-com/hyperframes

Steps to reproduce

  1. Install Hyperframes on a Crostini container
  2. Run npx hyperframes preview in any Hyperframes project directory

Expected behavior

Open the preview of hyperframes code

Actual behavior

┌  hyperframes preview
│
◇  Failed to start studio

  Ports 3002–3101 are all in use. Use --port to specify a different starting port.

Trying --port with any range gives the same result. Killing all node processes beforehand makes no difference.

Environment

- **OS:** Crostini (Debian 12 "bookworm" — ChromeOS Linux container)
- **Kernel:** 6.6.99-09128-g14e87a8a9b71
- **Node.js:** v24.13.1
- **npm:** 10.8.2
- **Hyperframes CLI:** 0.4.3

Additional context

No response

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions