fix(client): exclude aborted subrequests from batch messages - #2176
Conversation
…batch A batched call aborted after its request message was posted, but before the batch was sent, left both its request and a cancel message in the batch. The server counts every message against its maxSize, so a full batch with one cancelled call was rejected with 413 and every other call in it failed. Below the limit, the server still ran the cancelled call. The batch request body was also the live message array, so a cancel posted after the batch was handed to the transport, but before the transport read the body (e.g. while an async `origin` resolved), could still end up in the request and trigger the same 413. Send only the messages of calls that are still open, as a copy taken when the batch is sent. Fixes #2172 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbSdQ6v2aYW1fT6FmbkS7g
Drop comments that restate what the batch tests already show, keep only the reason the server-side size limit matters, and read the sent batch messages from the transport mock instead of a captured variable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbSdQ6v2aYW1fT6FmbkS7g
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
orpc | 0e1fc5d | Commit Preview URL Branch Preview URL |
Oct 04 2026, 07:45 AM |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the full diff (2 files, 2 commits) and traced the abort lifecycle through ClientPeer (@standard-server/peer) and BatchHandlerPlugin.
- Filtered batch payload —
executeBatchnow snapshotsoutgoingMessages = pendingMessages.filter(m => openRequestIds.has(m.id))afterisBatchSent = true, so a subrequest aborted (and itscancelmessage) before send drops out of both the GETdataquery param and the POST/QUERY body. - Boundary is
isBatchSent— cancels arriving later are still tracked viacancelledRunningRequestIdsand only abort the whole transport request when every open subrequest is cancelled, so an in-flight batch is never rewritten. - Tests — the pre-send abort case is now parameterized over POST/GET and asserts the batch carries only the open request; a new test aborts inside
transport.sendand pins that the sent messages stay unchanged.
The openRequestIds-keyed filter is sound: every open id is guaranteed to have a matching request message (the id is added before markRequestSent, removed only by a cancel), and cancels for open requests cannot exist, so filtering is equivalent to keeping exactly the still-open request messages in order. pendingMessages is no longer referenced after the snapshot, so later pushes cannot mutate the payload.
Verified locally: 59/59 batch.test.ts tests pass, pnpm --filter @orpc/client type:check passes, eslint clean.
The pre-send abort test genuinely fails against the old payload ([['request','/b']] vs. ['request','cancel','request']), so the coverage is real. QUERY is exercised via the shared else body branch even though the parameterized test only names POST/GET.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
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: |
Merging this PR will improve performance by 12.02%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | serve deeply nested encoded path |
1.2 ms | 1 ms | +12.02% |
Tip
Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.
Comparing claude/dazzling-gates-jdyqmh (0e1fc5d) with main (161cdc0)

Fixes #2172
Summary
Fixes batch link plugin to exclude subrequests that are aborted before the batch is sent to the transport. This prevents the server from counting cancelled requests against its batch size limit and avoids executing calls the client has already rejected.
Changes
Implementation Details
outgoingMessagesvariable that filterspendingMessagesto only include those with IDs inopenRequestIdsopenRequestIdsSet is already maintained by the plugin to track which subrequests are still pending, so this reuses existing tracking logichttps://claude.ai/code/session_01KbSdQ6v2aYW1fT6FmbkS7g