heygen-com/hyperframes · error

No free port found in [${startPort}, ${startPort + 9}]

Error message

No free port found in [${startPort}, ${startPort + 9}]

What it means

listenOnFreePort scans 10 consecutive ports starting at startPort; each EADDRINUSE causes a continue to the next candidate. The error fires only after all 10 (startPort .. startPort+9) are in use, or a non-EADDRINUSE error was re-thrown earlier. The function is used by the play/present dev servers to bind a preview port.

Source

Thrown at packages/cli/src/utils/compositionServer.ts:165

        const onErr = (err?: NodeJS.ErrnoException) => {
          server.removeListener("listening", onOk);
          rej(err ?? new Error("server error"));
        };
        const onOk = () => {
          server.removeListener("error", onErr);
          res();
        };
        server.once("error", onErr);
        server.once("listening", onOk);
        server.listen(port);
      });
      return port;
    } catch (err: unknown) {
      if ((err as NodeJS.ErrnoException).code === "EADDRINUSE") continue;
      throw err;
    }
  }
  throw new Error(`No free port found in [${startPort}, ${startPort + 9}]`);
}

View on GitHub (pinned to c2996c8626)

Solutions

  1. Free the ports: identify and kill processes holding startPort..startPort+9 (lsof -i :3000-3009 or netstat).
  2. Pass a different startPort in a less-contended range (e.g. 49152+, the ephemeral band).
  3. Reduce the number of concurrent preview servers running on the same host.
  4. Wait for TIME_WAIT to clear (usually 30-60s) if a preview was just stopped, then retry.
Defensive patterns

Strategy: retry

Validate before calling

import { createServer } from 'node:net';

async function isPortFree(port: number): Promise<boolean> {
  return new Promise((res) => {
    const s = createServer();
    s.once('error', () => res(false));
    s.once('listening', () => { s.close(() => res(true)); });
    s.listen(port);
  });
}

// probe a candidate startPort before passing it in
const ok = await isPortFree(startPort);
if (!ok) throw new Error(`Port ${startPort} busy; choose another.`);

Try / catch

try {
  return await listenOnFreePort(server, startPort);
} catch (err) {
  if (err instanceof Error && /No free port found/.test(err.message)) {
    // shift to a less-contended range and retry once
    return listenOnFreePort(server, 49152);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling listenOnFreePort with a startPort whose entire 10-port window is occupied — e.g. startPort=3000 and 3000-3009 all bound by other dev servers, browser instances, or orphaned hyperframes processes. Also when a previous preview server did not release its port (TIME_WAIT or unref'd process still holding the socket).

Common situations: Many dev servers running on adjacent ports; a hung/orphaned hyperframes preview from a previous run; Docker/WSL port forwarding consuming the range; CI runners with many concurrent preview builds sharing a port band.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/2923b7b9e507bb69. Report an issue: GitHub.