Skip to content

feat(client)!: don't retry 4xx errors by default in retry plugin - #2266

Merged
dinwwwh merged 1 commit into
mainfrom
claude/practical-tesla-s1ed2o
Oct 19, 2026
Merged

dinwwwh merged 1 commit into
mainfrom
claude/practical-tesla-s1ed2o

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Summary

RetryLinkPlugin no longer retries errors from a 4xx response by default, except 408 and 429, 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

  • New isRetryableError(error) exported from @orpc/client/plugins and used as the default shouldRetry. It reads the status from the ResponseError cause of a decoded ORPCError (added in #2265), or from a MalformedResponseError. Custom shouldRetry functions can build on it.
  • Status only: whether the error is defined does not matter. A defined code missing from the handler's errorStatusMap is sent as 500 and is retried.
  • Docs: new "Client Error Responses" section on the retry plugin page, plus a v1 migration note.
  • Tests: a status table for isRetryableError, plugin tests, and an e2e test (tests/rpc/retry.test.ts) running a real RPCHandler and RPCLink.

Breaking Change

The default shouldRetry changes from true to (o) => isRetryableError(o.error). To keep the previous behavior:

new RetryLinkPlugin({ default: { shouldRetry: true } })

🤖 Generated with Claude Code

https://claude.ai/code/session_01TT8g89r9wLou1gmzXeQuaQ

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
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@dinwwwh dinwwwh changed the title fix(client): don't retry 4xx errors by default in retry plugin feat(client)!: don't retry 4xx errors by default in retry plugin Oct 19, 2026
@pkg-pr-new

pkg-pr-new Bot commented Oct 19, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@2266

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@2266

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@2266

@orpc/client

npm i https://pkg.pr.new/@orpc/client@2266

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@2266

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@2266

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@2266

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@2266

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@2266

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@2266

@orpc/experimental-lock

npm i https://pkg.pr.new/@orpc/experimental-lock@2266

@orpc/experimental-msw

npm i https://pkg.pr.new/@orpc/experimental-msw@2266

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@2266

@orpc/next

npm i https://pkg.pr.new/@orpc/next@2266

@orpc/node

npm i https://pkg.pr.new/@orpc/node@2266

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@2266

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@2266

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@2266

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@2266

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@2266

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@2266

@orpc/server

npm i https://pkg.pr.new/@orpc/server@2266

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@2266

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@2266

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@2266

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@2266

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@2266

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@2266

commit: 1cbf02e

@codecov

codecov Bot commented Oct 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

⚠️ 5 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 28 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing claude/practical-tesla-s1ed2o (1cbf02e) with main (f90363c)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@pullfrog pullfrog Bot 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.

✅ No new issues found.

Reviewed changes

  • Retry policy — isRetryableError() now gates the plugin's default shouldRetry, skipping 4xx responses except 408 and 429, while still retrying 5xx, malformed non-4xx responses, and errors without a resolvable ResponseError.
  • Plugin default — RetryLinkPlugin uses (o) => isRetryableError(o.error) instead of unconditional true; 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).

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh merged commit 7760fc6 into main Oct 19, 2026
12 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