cypress-io / cypress-io/github-action

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

Open
#1,869 12 comments 0 reactions 0 assignees View on GitHub
bug topic: waiting
Dominant language
JavaScript
Stars
1.5k
Forks
353
Avg merge
1d 22h
Merged PRs (30d)
27

Description

## 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:

```js
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:

```yaml
- 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.

Contributor guide

Open the contributing guide

Research direction

Start at ping() in src/ping.js and trace the existing got retry and calculateDelay path used by the start and wait-on health check. Reproduce the first-attempt failure with a server that is not listening, then verify that the health check continues retrying connection-closed failures and eventually succeeds once the server starts.

Written by the indexing model from the issue text.

Assessment

Tech stack
github-actions, javascript, nodejs
Domain
ci-cd, devops, testing
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.