Skip to content

ping() stops retrying ECONNREFUSED after Node.js 24.20.0+ (upstream got bug) — start-server health check fails immediately instead of waiting for the dev server #1869

Description

@BowlesCR

Title

ping() stops retrying ECONNREFUSED after Node.js 24.20.0+ (upstream got bug) — start-server health check fails immediately instead of waiting for the dev server

Body

Describe the bug

src/ping.js's ping() (used for the start/health-check step) relies on got's built-in retry to wait out a dev server that hasn't started listening yet, special-casing ECONNREFUSED for a fast 1s retry:

if (error.code === 'ECONNREFUSED') {
  return 1000
}

Since Node.js 24.20.0, this stops working entirely. got no longer retries a connection-refused failure at all — it fails after a single attempt with a generic ERR_SOCKET_CLOSED_BEFORE_CONNECTION instead of ECONNREFUSED, so calculateDelay is never even reached for a second attempt. Any workflow using this action's start/wait-on-style health check against a server that isn't up yet on the first ping will fail immediately instead of waiting.

Root cause

This is an upstream bug in got — filed in detail at sindresorhus/got#2469, including a Node.js bisect that pins it to nodejs/node#64847 (a _http_outgoing.js fix shipped in Node 24.20.0). Short version: Node's end() callback now correctly receives the connection error where before it silently never fired at all; got's retry state machine treats that callback firing as immediately terminal instead of retrying.

Confirmed this is not limited to the got@11.8.6 pinned here — it reproduces identically on current got@latest (15.1.0), so there's no drop-in version bump available to fix it today.

Reproduction

Any job on a Node.js 24.20.0+ self-hosted runner (or any environment where the action itself runs under Node 24.20.0+, since ping.js executes in the action's own Node process) using this action's health-check against a server that takes >0 attempts to come up:

- uses: cypress-io/github-action@v7
  with:
    start: npm start
    wait-on: 'http://localhost:3000'

fails on the first ECONNREFUSED instead of waiting for the server.

Suggested workaround (doesn't require a got fix)

Since got's own retry logic never gets invoked for this failure (only one connection attempt is ever made), fixing this doesn't strictly require waiting on the upstream got fix. ping() could catch ERR_SOCKET_CLOSED_BEFORE_CONNECTION at the outer call site and retry manually, e.g. treating it the same as an ECONNREFUSED for the purposes of the existing retry/backoff loop, until the upstream got bug is fixed and a compatible version can be adopted (got is also several breaking major versions ahead of the pinned 11.8.6 — v12+ is ESM-only — so that'll be a separate migration).

Happy to share our full repro/trace if useful — see sindresorhus/got#2469 for the complete writeup.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions