Repository navigation
feat(client)!: don't retry 4xx errors by default in retry plugin - #2266
Conversation
The default `shouldRetry` of `RetryLinkPlugin` no longer retries errors from a 4xx response other than 408 and 429, since retrying them would most likely fail the same way. The status is read from the `ResponseError` cause of a decoded `ORPCError`, or from a `MalformedResponseError`. Everything else is still retried, including network failures, 5xx responses, and errors thrown mid-stream. The check is exported as `isRetryableError` so a custom `shouldRetry` can extend the default. Set `shouldRetry: true` to keep the v1 behavior. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TT8g89r9wLou1gmzXeQuaQ
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
orpc | 1cbf02e | Commit Preview URL Branch Preview URL |
Oct 19 2026, 08:49 AM |
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-lock
@orpc/experimental-msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Retry policy —
isRetryableError()now gates the plugin's defaultshouldRetry, skipping 4xx responses except408and429, while still retrying 5xx, malformed non-4xx responses, and errors without a resolvableResponseError. - Plugin default —
RetryLinkPluginuses(o) => isRetryableError(o.error)instead of unconditionaltrue; per-call and constructor overrides still take precedence. - Tests — unit coverage for the status matrix and error shapes, plus a new RPC end-to-end suite covering validation 400, defined 409, 503 retry, defined errors sent with 500, and mid-stream iterator reconnection.
- Docs — new "Client Error Responses" section and updated defaults in
plugins/retry.mdx, with a v1 migration note showing how to restore retry-all behavior.
Verified with pnpm vitest run packages/client tests/rpc/retry.test.ts (659 tests pass), ESLint on the changed files, both the package and root TypeScript builds, and scripts/verify-docs-jsdoc.ts (0 errors).
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Summary
RetryLinkPluginno longer retries errors from a 4xx response by default, except408and429, since retrying them would most likely fail the same way. Everything else is still retried: network failures, 5xx responses, and errors thrown mid-stream by an AsyncIteratorObject.Changes
isRetryableError(error)exported from@orpc/client/pluginsand used as the defaultshouldRetry. It reads the status from theResponseErrorcause of a decodedORPCError(added in #2265), or from aMalformedResponseError. CustomshouldRetryfunctions can build on it.errorStatusMapis sent as500and is retried.isRetryableError, plugin tests, and an e2e test (tests/rpc/retry.test.ts) running a realRPCHandlerandRPCLink.Breaking Change
The default
shouldRetrychanges fromtrueto(o) => isRetryableError(o.error). To keep the previous behavior:🤖 Generated with Claude Code
https://claude.ai/code/session_01TT8g89r9wLou1gmzXeQuaQ