Skip to content

createEmulator: await 'listening' so a port-in-use rejects instead of crashing #25

Description

@amondnet

Context

Split out from a cubic review finding on #24 (single-process launcher scripts/emulate-all.ts).

scripts/emulate-all.ts was hardened so that startVercel awaits the HTTP server's 'listening' event and rejects on 'error' — an occupied Vercel-service port now rejects into main()'s try/catch, which closes the services already started instead of crashing the launcher on an asynchronous listen error.

The this-repo side (startPlease) has the same latent issue, but the readiness lives one layer down in @pleaseai/emulate's createEmulator, which is outside the launcher PR's scope.

Problem

packages/emulate/src/api.tscreateEmulator calls serve({ fetch, port }) and returns immediately, without awaiting 'listening':

const httpServer = serve({ fetch: app.fetch, port })
return { url: baseUrl, /* ... */ }

serve() (from @emulators/core) returns a Node http.Server that binds asynchronously. If the port is occupied, the failure surfaces on the server's 'error' event (EADDRINUSE) on a later tick — not as a throw from createEmulator. So an awaited createEmulator(...) resolves successfully even though the port never bound, and the EADDRINUSE becomes an uncaught exception that crashes the process.

Proposed fix

Make createEmulator await readiness before resolving, mirroring the launcher fix:

const httpServer = serve({ fetch: app.fetch, port })
await new Promise<void>((resolve, reject) => {
  function onListening() { httpServer.off('error', onError); resolve() }
  function onError(err: Error) { httpServer.off('listening', onListening); reject(err) }
  httpServer.once('listening', onListening)
  httpServer.once('error', onError)
})

Then startPlease in the launcher inherits correct port-in-use handling automatically.

Notes

  • Low severity: affects a dev-time emulator launcher on a port collision, not production paths.
  • Verify serve() doesn't already attach its own conflicting 'error' handler in @emulators/core.

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