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
- Install Hyperframes on a Crostini container
- 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
- Install Hyperframes on a Crostini container
- 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
Describe the bug
Environment
Problem
npx hyperframes previewalways fails on Crostini, regardless of which port is specified or whether all existing processes are killed first.Steps to reproduce
npx hyperframes previewin any Hyperframes project directoryOutput
Trying
--portwith any range gives the same result. Killing all node processes beforehand makes no difference.Root cause
I found the issue in
cli.js. ThetestPortOnAllHostsfunction checks 4 host bindings in parallel usingPromise.all:isPortAvailableOnHostworks by temporarily binding a TCP server to the port, then closing it:The race condition:
Promise.allfires all 4 binds at the same time. The127.0.0.1bind succeeds and its socket stays open while it waits forserver.close(). Meanwhile,0.0.0.0(wildcard IPv4) and::(wildcard IPv6) both attempt to bind — and they seeEADDRINUSEbecause0.0.0.0/::are wildcard addresses that include127.0.0.1. They are colliding with the first test's still-open socket.testPortOnAllHostsreturnsfalsefor 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:
Output:
0.0.0.0and::always returnEADDRINUSEbecause127.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:
Alternatively, testing only
0.0.0.0(covers all IPv4 including127.0.0.1) and::(covers all IPv6 including::1) would be sufficient and avoids the redundancy that creates the race.Workaround
This serves the raw HTML files but without the studio timeline UI.
Link to reproduction
https://github.com/heygen-com/hyperframes
Steps to reproduce
npx hyperframes previewin any Hyperframes project directoryExpected behavior
Open the preview of hyperframes code
Actual behavior
Trying
--portwith any range gives the same result. Killing all node processes beforehand makes no difference.Environment
Additional context
No response