Visitar URL original
fix(core): resolve cross-runtime fetch and error-handling issues in gaxios, teeny-request, and gcp-metadata by danieljbruce · Pull Request #9519 · googleapis/google-cloud-node · GitHub
Skip to content

fix(core): resolve cross-runtime fetch and error-handling issues in gaxios, teeny-request, and gcp-metadata - #9519

Draft
danieljbruce wants to merge 1 commit into
googleapis:mainfrom
danieljbruce:fix/bun-client-library-issues
Draft

danieljbruce wants to merge 1 commit into
googleapis:mainfrom
danieljbruce:fix/bun-client-library-issues

Conversation

@danieljbruce

Copy link
Copy Markdown
Contributor

Summary

Fixes production client library issues across gaxios, teeny-request, and gcp-metadata exposed when running on non-V8 / standard fetch runtimes (such as Bun):

  1. teeny-request request timeouts (src/index.ts):

    • requestToFetchOptions previously only passed {timeout: reqOpts.timeout}, a non-standard node-fetch extension ignored by standard fetch / bun:node-fetch.
    • Now also passes signal: AbortSignal.timeout(reqOpts.timeout) when reqOpts.timeout is configured so timeouts are enforced across runtimes.
  2. gaxios and teeny-request proxy & mTLS options (gaxios/src/gaxios.ts, teeny-request/src/agents.ts, teeny-request/src/index.ts):

    • Runtimes whose fetch implementation ignores init.agent (such as Bun) previously ignored HTTPS_PROXY/HTTP_PROXY/proxy and mTLS cert/key configuration, or failed to bypass opts.proxy when noProxy matched.
    • gaxios (_defaultAdapter) and teeny-request (requestToFetchOptions) now populate proxy and tls options on the fetch init object alongside agent, and ensure proxy is omitted when noProxy / NO_PROXY excludes the target URL.
  3. gaxios non-Error abort reasons and timeout retries (gaxios/src/gaxios.ts, gaxios/src/retry.ts):

    • When AbortController.abort(reason) is called with a non-Error reason (e.g. a string), standard fetch rejects with signal.reason verbatim. Gaxios._request previously fell into new GaxiosError('Unexpected Gaxios Error', ...) without code = 'AbortError' and discarded the reason string. It now preserves the reason in the error message and sets err.code = 'AbortError'.
    • When AbortSignal.timeout triggers, node-fetch throws a generic AbortError (code = undefined) even though opts.signal.reason is DOMException { name: 'TimeoutError' }, causing timeout retry behavior to diverge between node-fetch and native/Bun fetch. Gaxios._request and shouldRetryRequest now inspect signal.reason so timeout retries behave consistently across runtimes.
  4. teeny-request Web ReadableStream response body handling (teeny-request/src/index.ts):

    • In stream mode, teenyRequest now converts Web ReadableStream bodies (!(res.body instanceof Readable)) to Node stream.Readable via Readable.fromWeb before attaching stream listeners, while avoiding locking res.body prior to res.json() / res.text() in callback mode and safely handling null response bodies (such as 204 No Content).
  5. gcp-metadata empty AggregateError.message (gcp-metadata/src/index.ts):

    • When both primary and secondary metadata requests reject in fastFailMetadataRequest (Promise.any), JavaScriptCore (Bun) constructs an AggregateError with an empty .message (""), causing MetadataLookupWarning in isAvailable() to emit received unexpected error = code = ....
    • fastFailMetadataRequest now normalizes an empty AggregateError.message, and isAvailable() falls back to extracting child error messages when e.message is empty.

Testing

  • Added unit tests in core/packages/gcp-metadata/test/index.test.ts, core/packages/teeny-request/test/index.ts, core/packages/gaxios/test/test.getch.ts, and core/packages/gaxios/test/test.retry.ts.

@danieljbruce
danieljbruce requested a review from a team as a code owner October 5, 2026 18:38
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@github-actions
github-actions Bot requested a review from bshaffer October 5, 2026 19:04
@danieljbruce
danieljbruce marked this pull request as draft October 5, 2026 20:44
@danieljbruce
danieljbruce force-pushed the fix/bun-client-library-issues branch from 702f2e9 to e8fd33e Compare October 6, 2026 17:55
… teeny-request, and gcp-metadata

- teeny-request: delegate default `fetch` on Bun to `globalThis.fetch` (or `globalThis.__googleCloudBunFetch` when shimmed) with `Readable.toWeb` request stream conversion so `import('node-fetch')` is not hijacked by `bun:node-fetch` (which bypasses `globalThis.fetch` and `nock` v14), pass `signal: AbortSignal.timeout(reqOpts.timeout)` in `requestToFetchOptions`, populate `proxy` and `tls` options alongside `agent` while respecting `NO_PROXY`, and convert Web `ReadableStream` responses to Node `stream.Readable` via `Readable.fromWeb` in stream mode without prematurely locking the stream in callback mode.
- gaxios: delegate `Gaxios.#getFetch()` on Bun to `globalThis.fetch` (or `globalThis.__googleCloudBunFetch` when shimmed) with `Readable.toWeb` request stream conversion and lazy `Readable.fromWeb` response stream wrapping, forward `proxy` and `tls` options to `fetchImplementation` in `_defaultAdapter` (and remove `proxy` when `noProxy` matches), preserve non-`Error` `AbortController.abort(reason)` values with `code = 'AbortError'` instead of falling back to `'Unexpected Gaxios Error'`, and normalize `TimeoutError` signal reasons and messages across `node-fetch` and native `fetch` (`V8` and `JavaScriptCore`).
- gcp-metadata: pass `fetchImplementation` on Bun so metadata requests route through `globalThis.fetch`, unwrap nested error codes (`AggregateError.errors`, `.cause`, `.error`) in `isAvailable`, and ensure `AggregateError` from `Promise.any` in `fastFailMetadataRequest` and `isAvailable` (`MetadataLookupWarning`) always has a non-empty error message on runtimes (such as `JavaScriptCore`/Bun) where `Promise.any` initializes `AggregateError.message` to `""`.
@danieljbruce
danieljbruce force-pushed the fix/bun-client-library-issues branch from e8fd33e to f2f15d1 Compare October 6, 2026 18:02

This branch has not been deployed

No deployments
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.

1 participant