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.
Error: listen EADDRINUSE: address already in use :::3000
# or
bind: address already in useWhy 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:
- Re-run the job so the port has time to release.
- Add a short retry loop around the bind, or wait for the port to be free before starting.
- 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.