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