Skip to content
Latchkey

Self-Healing CI: Recovering "Address Already in Use" Port Races

A port that is "already in use" for a second is usually a previous process releasing it -- the same bind succeeds on a clean retry.

The problem

A step that starts a server or test harness fails with EADDRINUSE / address already in use. No other service truly wants that port; a prior process from the same job was still releasing it, or two steps raced to bind. A human re-runs the job, or waits a moment, and the bind succeeds unchanged.

Typical symptom
Error: listen EADDRINUSE: address already in use :::3000
# or
bind: address already in use

Why it happens

A socket can linger briefly in a closing state after the previous owner exits, so a new process binding the same port a moment later is transiently refused even though the port is about to be free.

It is a timing race, not a real conflict: a short wait or a retry lets the previous socket fully release, and the same bind then succeeds with no change to the job.

The manual fix

Manual handling for a port race:

  1. Re-run the job so the port has time to release.
  2. Add a short retry loop around the bind, or wait for the port to be free before starting.
  3. Use an ephemeral/dynamic port instead of a fixed one where possible.

How this gets automated

An "address already in use" at startup has a recognizable transient signature, and the safe response is to wait briefly and retry the bind. A self-healing CI pipeline detects the port-race condition, retries once the socket releases, and only escalates if the port stays occupied, which is the real signal of a genuine conflict rather than a release lag.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →