Short answer
“Max retry time exceeded” means Octokit.js consumed its retry budget; it does not by itself prove connection-pool exhaustion. The most reliable isolation is per-request correlation: log the terminal HTTP status and headers, and at the moment of failure sample the process file-descriptor count against ulimit -n and socket states.
Correlate the failure with three signals
Run the load test while collecting these samples every second:
- Octokit response status and headers. A terminal
429 with X-RateLimit-Remaining: 0 is rate limiting. A terminal 502/503 is transient upstream. Neither is pool exhaustion.
- File-descriptor usage:
ls /proc/<pid>/fd | wc -l compared with ulimit -n. Failures that coincide with the fd count pinned at the limit—or with EMFILE—point to fd exhaustion.
- Socket states:
ss -tan state close-wait and ss -tan state time-wait. Large CLOSE_WAIT counts indicate sockets the peer closed but the process has not destroyed; large TIME_WAIT counts indicate ephemeral-port pressure.
If failures align with fd saturation or EMFILE, the root cause is client-side pool/fd exhaustion. If failures align with 429 or 5xx, the retry budget was simply too small for the upstream condition.
Keep-alive and Connection: close
Yes. Enabling HTTP-agent keep-alive and destroying sockets when a response carries Connection: close is the correct mitigation. Keep-alive reuses ESTABLISHED sockets, reducing ephemeral-port and TIME_WAIT pressure. Honoring Connection: close prevents server-closed sockets from lingering in the free pool and becoming CLOSE_WAIT.
Idle keep-alive sockets closed by GitHub can still sit briefly in CLOSE_WAIT, so also set an idle socket timeout and destroy sockets on error. Against a load balancer that closes idle connections, keep-alive without these settings can increase CLOSE_WAIT.
Is the error tied to ulimit -n?
Not inherently. The error is independent of the fd limit only if fd usage stays well below ulimit -n and sockets are mostly ESTABLISHED. If the fd count is pinned at the limit or EMFILE appears, raising ulimit -n and reusing connections is the fix. If retries are exhausted by a burst of genuine 502/503, fd limits are irrelevant and the fix is backoff that honors Retry-After.
Version assumptions and verification
Defaults are version-sensitive. Node changed the global agent keepAlive default from false before v19 to true from v19. Octokit’s request layer and retry status list have also changed across releases. Confirm the installed versions and the agent actually used before tuning:
node -v
npm ls @octokit/core @octokit/plugin-retry @octokit/request
ulimit -n
Because these defaults vary, treat the following as a conservative checklist rather than a guaranteed fix: verify the agent’s keepAlive setting, confirm Connection: close handling, and re-run the correlated sampling above.
One missing diagnostic detail
If you can share the installed @octokit/* versions and Node version, that changes which agent defaults apply and whether retry behavior needs explicit configuration.