Skip to content

Keep handler errors from claiming the reserved SERVER_DRAINING code - #434

Merged
Armster15 merged 1 commit into
mainfrom
typebox-reserved-drain-code
Oct 23, 2026
Merged

Armster15 merged 1 commit into
mainfrom
typebox-reserved-drain-code

Conversation

@Armster15

Copy link
Copy Markdown
Contributor

Why

resendRefusedCalls resends an RPC when its result carries SERVER_DRAINING, because that code means the server refused the call before middleware or handlers ran. A procedure whose error schema includes ReaderErrorSchema or the literal 'SERVER_DRAINING' can return that code after it already ran. The client then resends work that has already happened.

What changed

  • When the TypeBox server writes a handler result, it remaps a handler-returned SERVER_DRAINING error to UNCAUGHT_ERROR with the same message and logs a warning. Only the runtime admission refusal can now send SERVER_DRAINING.
  • Add one behavior test in the existing drain test file.

Versioning

  • Breaking protocol change
  • Breaking ts/js API change

No wire change or version bump. A handler that returned SERVER_DRAINING now has its error delivered as UNCAUGHT_ERROR. That code was reserved for the drain refusal in 0.226.0.

Tests

  • Full suite: 815 passed, 1 skipped, across 37 files. npm run test:single exited 0.
  • npm run check exited 0: TypeScript, Prettier, and ESLint passed.
  • New test: a procedure records its run and returns SERVER_DRAINING; the client has resendRefusedCalls, and the test rotates the session. The procedure runs exactly once and the client receives UNCAUGHT_ERROR.
  • Red proof: disable the remap, and the procedure runs twice (['claim', 'claim']) instead of once. The remap was restored.
Appendix

Decisions

  • Remap at the single result writer, not in each handler path. Every final handler result passes through it, and the runtime refusal uses the separate cancel path, so it is unaffected.
  • Use UNCAUGHT_ERROR, the existing built-in code for handler failures, and keep the handler message. A warning log matches the existing handler-error logging.
  • No type narrowing. Procedure error types come from user schemas, and excluding one literal across all schema unions would ripple through the procedure typing. The runtime remap is the guard.
  • Keep this separate from the protobuf drain PR because the TypeBox behavior already shipped in 0.226.0.

Facts verified in code

  • router/client.ts:336: resend is keyed only on result.payload.code === SERVER_DRAINING_CODE.
  • router/server.ts:606-633: the handler result writer, now with the remap.
  • router/server.ts:242-254: the runtime refusal is sent via cancelStream, not the result writer.
  • router/server.ts:662-686: the existing UNCAUGHT_ERROR_CODE precedent for handler failures.

Review dispositions

  • Pass A: SHIP. Admission refusals still send SERVER_DRAINING, and all existing drain tests pass. A handler error with any other code is unchanged. No new casts or any.
  • Pass B: SHIP. One guarded branch in the existing writer, no new abstraction, and no test-only parameters.

~ written by ⠕ Replit

Only the runtime admission refusal may carry SERVER_DRAINING, so a
procedure that already ran is never resent. Remap a handler-returned
SERVER_DRAINING to UNCAUGHT_ERROR and log a warning.

Assisted-by: Replit
@Armster15
Armster15 marked this pull request as ready for review October 22, 2026 00:24
@Armster15
Armster15 requested a review from a team as a code owner October 22, 2026 00:24
@Armster15
Armster15 requested review from arnav-replit and removed request for a team October 22, 2026 00:24

@darshkpatel darshkpatel 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.

Discussed offline 🤷🏻

@Armster15
Armster15 merged commit c29ba09 into main Oct 23, 2026
9 checks passed
@Armster15
Armster15 deleted the typebox-reserved-drain-code branch October 23, 2026 22:43
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