Skip to content

feat(client): introduce ResponseError for carrying response details - #2265

Merged
dinwwwh merged 1 commit into
mainfrom
claude/sleepy-cerf-0hsbvy
Oct 19, 2026
Merged

dinwwwh merged 1 commit into
mainfrom
claude/sleepy-cerf-0hsbvy

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 19, 2026

Copy link
Copy Markdown
Member

Summary

Introduce a new ResponseError class that carries resolved response details (status, headers, body) when links encounter error responses. This allows callers to access response metadata that isn't part of the error itself, such as status codes and headers.

Changes

  • New ResponseError class: A lightweight error that wraps response details, used as the cause of ORPCError instances decoded from error responses
  • Refactored MalformedResponseError: Now extends ResponseError instead of Error, eliminating duplicate response handling logic
  • Updated link codecs: Both RPCLinkCodec and OpenAPILinkCodec now attach a ResponseError as the cause when decoding error responses, making response metadata available to error handlers
  • Documentation: Added "Error Responses" sections to RPC and OpenAPI link docs with examples of accessing response details via the cause chain
  • Tests: Added comprehensive tests for ResponseError and updated existing tests to verify the cause chain

Implementation Details

  • ResponseError infers a default message from the response status (e.g., "Received a 404 response.") when no custom message is provided
  • MalformedResponseError now delegates to ResponseError's constructor, reducing code duplication
  • The response is attached as cause rather than thrown directly, preserving the primary error (ORPCError) while keeping response details accessible
  • Both RPC and OpenAPI links follow the same pattern for consistency

https://claude.ai/code/session_01RggAhK2fPDzS1W965ZaJQY

Add `ResponseError`, an error carrying the resolved response a link
received. `RPCLink` and `OpenAPILink` now set it as the `cause` of the
`ORPCError` they decode from an error response, so its status and
headers stay available on the client. `MalformedResponseError` now
extends `ResponseError`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RggAhK2fPDzS1W965ZaJQY
@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 4a90670 Commit Preview URL

Branch Preview URL
Oct 19 2026, 08:12 AM

@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 — Introduces a public ResponseError carrying the resolved response and attaches it as the cause of ORPCErrors decoded from error responses; MalformedResponseError is refactored to extend it; docs and tests updated.

  • New ResponseError class (packages/client/src/error.ts:173) — extends Error, exposes response, and defaults its message to Received a ${status} response.; ResponseErrorOptions is extracted from the old malformed options.
  • MalformedResponseError extends ResponseError (packages/client/src/error.ts:204) — drops its duplicated response field while preserving message inference and name.
  • RPC and OpenAPI link codecs (rpc-link-codec.ts:147, openapi-link-codec.ts:410) — attach a ResponseError as cause when the error body is an ORPCError-shaped JSON; the OpenAPI customErrorResponseBodyDecoder path still returns its error as-is.
  • Docs + tests — new "Error Responses" sections in both link docs (anchors match the JSDoc @see links); tests assert the cause chain and that it is a plain ResponseError, not a MalformedResponseError.

Verified locally: the affected tests pass and @orpc/client / @orpc/openapi type-check cleanly. No framework source branches on the absence of error.cause, so attaching one is non-breaking for existing consumers.

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

@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@2265

@orpc/arktype

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

@orpc/bun

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

@orpc/client

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

@orpc/cloudflare

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

@orpc/contract

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

@orpc/experimental-effect

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

@orpc/evlog

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

@orpc/hibernation

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

@orpc/json-schema

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

@orpc/experimental-lock

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

@orpc/experimental-msw

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

@orpc/nest

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

@orpc/next

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

@orpc/node

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

@orpc/openapi

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

@orpc/opentelemetry

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

@orpc/pinia-colada

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

@orpc/pino

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

@orpc/publisher

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

@orpc/ratelimit

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

@orpc/server

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

@orpc/shared

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

@orpc/swr

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

@orpc/tanstack-query

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

@orpc/trpc

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

@orpc/valibot

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

@orpc/zod

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

commit: 4a90670

@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

⚠️ 6 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/sleepy-cerf-0hsbvy (4a90670) with main (0c7f63b)

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. ↩

@dinwwwh
dinwwwh merged commit f90363c into main Oct 19, 2026
12 checks passed
dinwwwh added a commit that referenced this pull request Oct 19, 2026
## 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](#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:

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01TT8g89r9wLou1gmzXeQuaQ

Co-authored-by: Claude <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.

2 participants