Skip to content

fix(browserstack-service): raise CLI download connect timeout to avoid 10s undici abort (SDK-6152)#6

Open
AakashHotchandani wants to merge 1 commit into
mainfrom
SDK-6152-cli-download-connect-timeout
Open

fix(browserstack-service): raise CLI download connect timeout to avoid 10s undici abort (SDK-6152)#6
AakashHotchandani wants to merge 1 commit into
mainfrom
SDK-6152-cli-download-connect-timeout

Conversation

@AakashHotchandani

Copy link
Copy Markdown
Owner

SDK-6152 — SDK CLI binary download times out after exactly 10s in containerised CI

Problem

@wdio/browserstack-service v9 downloads the SDK CLI binary (and calls update_cli) via Node's global fetch (undici). Undici hard-codes a 10s connection-establishment timeout that is not overridable via AbortSignal. In constrained/containerised CI networks (the reported case: Kubernetes pods) the TLS/TCP handshake to the CDN / api.browserstack.com can legitimately take longer than 10s, so the download aborts with UND_ERR_CONNECT_TIMEOUT even though the endpoint is healthy. The failure was logged only at debug, so the run then exited silently with no results.

This matches the customer evidence exactly: a standalone fetch() probe of the same URL succeeded (free event loop / fast connect), while the service's internal download died at exactly 10s.

Fix

  • fetchWrapper._fetch now accepts a connectTimeoutMs option and routes the request through a cached custom undici dispatcher (Agent / ProxyAgent) with the configured connect timeout. The default (no-option) path is unchanged (still global fetch).
  • requestToUpdateCLI and the binary download use it — default 60s, overridable via BROWSERSTACK_CLI_CONNECT_TIMEOUT_MS — plus retry-with-backoff (3 attempts) on transient connection failures.
  • CLI setup failures are surfaced at warn instead of debug so they're no longer silent.

Why a custom dispatcher (and not global fetch + dispatcher)

Global fetch uses Node's built-in undici; passing an Agent from the package undici to it fails with UND_ERR_INVALID_ARG (version mismatch). Going through undiciFetch from the package — the same pattern the existing proxy branch already uses — is the consistent, safe choice.

Verification

Against the real modified fetchWrapper.ts, using a local TCP server that accepts the socket but never completes TLS (deterministic stalled connect):

Path connect timeout result
default (unchanged) undici fixed 10s UND_ERR_CONNECT_TIMEOUT @ 10,541ms (reproduces the bug)
fixed connectTimeoutMs: 2000 timeout @ 2,545ms
fixed connectTimeoutMs: 6000 timeout @ 6,502ms
fixed, healthy CDN 60s connected @ 967ms (no regression)

→ the connect timeout now tracks the configured value instead of the hard 10s ceiling.

Unit tests: packages/wdio-browserstack-service/tests/cli/cliUtils.test.ts40/40 pass (updated requestToUpdateCLI tests to the new _fetch boundary + 3 new fetchWithRetry tests). ESLint clean on changed files; fetchWrapper.ts type-checks clean.

Note (server/CDN side, out of scope here)

The reported version trigger (binary 1.34.0 OK → 1.34.1/1.34.2 fail) is most plausibly CloudFront cache warmth / object-size changes around the new release pushing connect/transfer past the old 10s ceiling. This PR removes the client-side cliff; the CDN behaviour for new releases is worth a separate look.

🤖 Generated with Claude Code

…d 10s undici abort (SDK-6152)

Node's global fetch (undici) hard-codes a 10s connection-establishment timeout
that is NOT overridable via AbortSignal. In constrained/containerised CI
networks (e.g. Kubernetes pods) the TLS/TCP handshake to the SDK CLI binary CDN
and the update_cli API can legitimately exceed 10s, so the download aborts with
UND_ERR_CONNECT_TIMEOUT even though the endpoint is healthy. The error was
logged only at debug level, so the run then exited silently with no results.

- fetchWrapper._fetch accepts a `connectTimeoutMs` option and routes the request
  through a cached custom undici dispatcher (Agent / ProxyAgent) with the
  configured connect timeout. The default (no-option) path is unchanged.
- requestToUpdateCLI and the binary download now use it (default 60s, overridable
  via BROWSERSTACK_CLI_CONNECT_TIMEOUT_MS), plus retry-with-backoff on transient
  connection failures.
- CLI setup failures are surfaced at warn instead of debug so they are visible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants