{"record":{"id":"2923b7b9e507bb69","repo":"heygen-com/hyperframes","slug":"no-free-port-found-in-startport-startport","errorCode":null,"errorMessage":"No free port found in [${startPort}, ${startPort + 9}]","messagePattern":"No free port found in \\[(.+?), (.+?)\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/utils/compositionServer.ts","lineNumber":165,"sourceCode":"        const onErr = (err?: NodeJS.ErrnoException) => {\n          server.removeListener(\"listening\", onOk);\n          rej(err ?? new Error(\"server error\"));\n        };\n        const onOk = () => {\n          server.removeListener(\"error\", onErr);\n          res();\n        };\n        server.once(\"error\", onErr);\n        server.once(\"listening\", onOk);\n        server.listen(port);\n      });\n      return port;\n    } catch (err: unknown) {\n      if ((err as NodeJS.ErrnoException).code === \"EADDRINUSE\") continue;\n      throw err;\n    }\n  }\n  throw new Error(`No free port found in [${startPort}, ${startPort + 9}]`);\n}\n","sourceCodeStart":147,"sourceCodeEnd":167,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/utils/compositionServer.ts#L147-L167","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Free the ports: identify and kill processes holding startPort..startPort+9 (lsof -i :3000-3009 or netstat).","Pass a different startPort in a less-contended range (e.g. 49152+, the ephemeral band).","Reduce the number of concurrent preview servers running on the same host.","Wait for TIME_WAIT to clear (usually 30-60s) if a preview was just stopped, then retry."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"import { createServer } from 'node:net';\n\nasync function isPortFree(port: number): Promise<boolean> {\n  return new Promise((res) => {\n    const s = createServer();\n    s.once('error', () => res(false));\n    s.once('listening', () => { s.close(() => res(true)); });\n    s.listen(port);\n  });\n}\n\n// probe a candidate startPort before passing it in\nconst ok = await isPortFree(startPort);\nif (!ok) throw new Error(`Port ${startPort} busy; choose another.`);","typeGuard":null,"tryCatchPattern":"try {\n  return await listenOnFreePort(server, startPort);\n} catch (err) {\n  if (err instanceof Error && /No free port found/.test(err.message)) {\n    // shift to a less-contended range and retry once\n    return listenOnFreePort(server, 49152);\n  }\n  throw err;\n}","preventionTips":["Kill orphaned preview servers between runs (lsof -i:<port>).","Use a startPort in the ephemeral range (49152+) where contention is lower.","Avoid running many concurrent dev servers on adjacent ports."],"tags":["network","port","dev-server","eaddrinuse","preview"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}