Skip to content

Retry HTTP 429 and 5xx responses for queries and mutations - #102

Merged
omichowdhury merged 9 commits into
devfrom
fix-retry-http-errors
Oct 27, 2026
Merged

omichowdhury merged 9 commits into
devfrom
fix-retry-http-errors

Conversation

@omichowdhury

@omichowdhury omichowdhury commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

The SDK's retry wrapper only retried GraphQL-level retryable errors on mutations. A query hitting an HTTP 503 (API Gateway timing out its Lambda invoke) failed on the first attempt, which is how a customer saw a 503 on a balance read.

  • Queries and mutations now share one request path. Network errors and HTTP 429/5xx are retried per retryConfig; other statuses throw a FragmentError immediately.
  • Mutations previously retried every HTTP error, 4xx included, and surfaced graphql-request's raw ClientError. They now surface FragmentError with status like queries.
  • Thrown errors other than node-fetch connection failures (e.g. AbortError) are not retried.
  • FragmentError.cause is now typed to include the mutation error payload it already carried.

🤖 Generated with Claude Code

omichowdhury and others added 4 commits October 22, 2026 12:10
Queries bail on any HTTP error and mutations retry every HTTP error,
including 4xx, and surface graphql-request's raw ClientError.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
API Gateway returns an occasional 503 when its Lambda invoke stalls. The
query path passed every ClientError to bail(), so a balance read failed
on the first attempt. Both operation types now send the request through
one path: 429/5xx throws a FragmentError so async-retry retries it, any
other status bails with the same FragmentError. Mutations previously let
the raw ClientError escape and retried 4xx responses too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A connection reset or DNS failure is as transient as a 503. Errors that
are not a ClientError propagate out of the retry callback so async-retry
retries them, and the last one is what the caller sees.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@omichowdhury
omichowdhury force-pushed the fix-retry-http-errors branch from ae94e49 to d01de99 Compare October 22, 2026 16:10
omichowdhury and others added 3 commits October 27, 2026 10:46
Each attempt resolves to either data or an error tagged retryable, so the
retry callback has one throw site, one bail site and no fall-through after
bailing on a multi-key mutation response. The mutation payload is typed
instead of cast through `any`, and the unknown-operation-type guard runs
before retry() starts.

Tests await the rejection matchers directly instead of capturing the error
with `.catch`, share one `makeClient`, and cover a mutation that succeeds
after two 503s.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A rejection that is not a ClientError is retried only when it is
node-fetch's FetchError of type "system". An AbortError or a bug in the
SDK surfaces on the first attempt instead of after the backoff.

FragmentError.cause now admits the mutation error payload it has always
carried at runtime, so client.ts no longer launders it as an Error.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Attempts are counted at the mock server instead of through onRetry, so
the tests exercise DEFAULT_RETRY_CONFIG as shipped. The README Retries
section is dropped; the defaults are what callers get.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@omichowdhury
omichowdhury marked this pull request as ready for review October 27, 2026 17:09

@vigneshwerv vigneshwerv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One nit: Could you refactor out the ternary statements?

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@omichowdhury

Copy link
Copy Markdown
Contributor Author

Ternaries replaced with if branches in 19d9e2f (classifyRequestError, the default case of classifyMutationData, the data classifier in the request wrapper, and the two-failure msw handler in the tests).

🤖 Claude Code

@omichowdhury
omichowdhury merged commit e6af724 into dev Oct 27, 2026
4 checks passed
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.

2 participants