fix(core): fail rpc.internal as a typed error on every path - #46902
Closed
kitlangton wants to merge 2 commits into
Closed
fix(core): fail rpc.internal as a typed error on every path#46902kitlangton wants to merge 2 commits into
kitlangton wants to merge 2 commits into
Conversation
Contributor
Author
|
Superseded by #46946, which keeps the handler-error fix and adds only seven focused regression cases. All seven fail on the unfixed base; the broader test-suite restoration is left out. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Rpc.client(definition)is typedRpcClient<D, Rpc.SystemError, ...>, andSystemErrorincludes"rpc.internal", but core never produced it. A handler that died, threw, failed with a raw value (the Promise adapter path), or used an undeclaredcontext.error(...)type surfaced as an uncatchable defect for in-process callers (plugin → plugin), while an HTTP caller of the same handler got a catchable{ type: "rpc.internal" }from the server'scatchDefect. The two transports disagreed about the error contract, and the server's safety net also echoed arbitrary handler exception text to HTTP clients, which no other handler does.Separately, #46639 deleted every test for the RPC subsystem added in #46105 (
core/test/rpc.test.ts,core/test/plugin/rpc-{effect,promise}.test.ts,server/test/rpc.test.ts). This PR restores them first so the fix lands against a real regression suite.What Changes
packages/core/src/rpc.ts—Rpc.callnow converts every non-declared handler outcome into a typedRpc.Failurewithtype: "rpc.internal":Each conversion runs
Effect.logError("rpc handler failed", { rpc, method, error })exactly once so the underlying cause is not lost. TheEffect.catch→Effect.catchDefectorder means a declared error whosedatafails its own schema also lands asrpc.internal(it is a handler bug, not a caller error).rpc.invalid_outputis unchanged.encodeErrorwas inlined into the single call site.packages/server/src/handlers/rpc.ts— maps core'srpc.internal(alongsiderpc.invalid_output) toRpcInternalErrorso the HTTP status stays 500. ThecatchDefectsafety net remains but now returns the fixed message"RPC call failed"and logs the defect server-side instead of echoingerror.message.Rpc.Failure→ 400 mapping and all status codes are unchanged.Not touched:
Rpc.defineschema surface, method/event naming,packages/client,packages/protocol(no HttpApi change, nobun run generate), the Promise adapter.Restored tests
All restored from
6a2c3e91c7and adapted to HEAD (Plugin.awaitActivationinstead ofPluginSupervisor.flush,revisioninstead ofversiononPlugin.Generation,Node.replace(layer)/LayerNode.compile(root, { replacements })for the layer-node API,ctx.locationinstead of(yield* ctx.agent.list()).location,tmpdirScoped, no aliased imports).packages/core/test/rpc.test.tspackages/core/test/plugin/rpc-effect.test.tspackages/core/test/plugin/rpc-promise.test.tspackages/server/test/rpc.test.tsEvery restored assertion held on HEAD before the source change, so nothing else in the RPC subsystem regressed since #46105. One assertion was updated by the fix itself:
rpc-promise.test.tspreviously assertedclient.defect()rejects with the raw"handler defect"Error and now asserts the typed{ type: "rpc.internal", message: "RPC call failed" }rejection.New coverage for the contract:
core/test/rpc.test.ts— dies / throws / raw fail / undeclared error → in-processclient(def).method()fails (not dies) withrpc.internal, is recoverable viaEffect.catchIf, and each conversion logs once. Confirmed it dies on HEAD before the fix.server/test/rpc.test.ts— the same defect over HTTP returns 500 with"RPC call failed"and no exception text; undeclared error returns 500 withUndeclared RPC error: <type>.Verification
bun typecheckclean inpackages/core,packages/server,packages/pluginoxlintclean on all changed files--rerun-each 3: core 63/63 pass (3 files), server 9/9 passbun run testinpackages/core: 4018 pass, 39 skip, 1 fail —snapshot.test.ts > treats fatal ignore checks as unavailable captures, which passes in isolation on both this branch and cleanorigin/v2(order-dependent, unrelated to RPC)bun run ../core/script/test.tsinpackages/server: 53 pass, 3 skip (pre-existingpersistent-ptybinary smoke skips), 0 fail