RpcServer: with disableFatalDefects, a handler that throws before returning its Effect still fails every pending call
Maintainers usually reply within 1 day
Assessment
This issue has not been assessed yet.
Description
Written by an AI agent (Claude) on behalf of @jasonkuhrt.
What version of Effect is running?
4.0.4 (Node 24). The code involved is unchanged on main (53823f2, 4.0.6).
What steps can reproduce the bug?
A server built with disableFatalDefects: true, one call in flight, and a second call whose handler throws before it returns an Effect:
import { Deferred, Effect, Exit, Fiber, Schema } from "effect"
import { Rpc, RpcClient, RpcGroup, RpcServer } from "effect/rpc"
const Rpcs = RpcGroup.make(
Rpc.make("Pending", { success: Schema.String }),
Rpc.make("Throws", { success: Schema.String })
)
const program = Effect.gen(function*() {
const started = yield* Deferred.make<void>()
const release = yield* Deferred.make<string>()
const handlers = Rpcs.toLayer({
Pending: () => Effect.andThen(Deferred.succeed(started, undefined), Deferred.await(release)),
Throws: (): Effect.Effect<string> => {
throw new Error("thrown before returning an Effect")
}
})
yield* Effect.gen(function*() {
let client!: Effect.Success<ReturnType<typeof RpcClient.makeNoSerialization<RpcGroup.Rpcs<typeof Rpcs>, never>>>
const server = yield* RpcServer.makeNoSerialization(Rpcs, {
disableFatalDefects: true,
onFromServer: (response) => client.write(response)
})
client = yield* RpcClient.makeNoSerialization(Rpcs, {
supportsAck: true,
onFromClient: ({ message }) => server.write(0, message)
})
const pending = yield* Effect.forkChild(client.client.Pending())
yield* Deferred.await(started)
const threw = yield* Effect.exit(client.client.Throws())
yield* Deferred.succeed(release, "still served")
const pendingExit = yield* Effect.exit(Fiber.join(pending))
console.log("Throws:", Exit.isFailure(threw) ? "failed" : "succeeded")
console.log("Pending:", Exit.isSuccess(pendingExit) ? `succeeded with ${pendingExit.value}` : `failed: ${pendingExit.cause}`)
}).pipe(Effect.provide(handlers))
}).pipe(Effect.scoped)
Effect.runPromise(program)
What is the expected behavior?
The throw fails the Throws call alone, as an Exit.die, and Pending still succeeds. That is what happens when the handler returns Effect.die(...) instead of throwing:
Throws: failed
Pending: succeeded with still served
What do you see instead?
Pending fails with the error Throws's handler threw:
Throws: failed
Pending: failed: Cause([Die(Error: thrown before returning an Effect)])
Additional information
RpcServer.makeNoSerialization calls entry.handler(request.payload, metadata) (packages/effect/src/rpc/RpcServer.ts:281 on main) before the request's own exit handling, which is the only place disableFatalDefects is read (:328). A synchronous throw from the handler reaches the message loop's outer catch, and sendDefect answers the whole client with a connection-level Defect, so the client fails every pending call.
Turning a throw from entry.handler(...) into that request's Exit.die, through the same disableFatalDefects branch, would fix it.
Related: #9212, the client-side half, fixed in #9232.
- Dominant language
- TypeScript
- Stars
- 16.7k
- Forks
- 808
- Avg merge
- 10h 33m
- Merged PRs (30d)
- 830
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from Effect-TS/effect
-
RpcTest.makeClient: accept disableFatalDefectsPossibly taken @effect-bot claimed this 1 day ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Effect-TS/effect#9893 · 3 comments ·
Maintainers usually reply within 1 day
-
`HttpApiSchema.Upgrade`: allow a regular success for servers that don't upgradePossibly taken @effect-bot claimed this today. Opendocker-made-me-do-it enhancement
Effect-TS/effect#9912 · 1 assignee ·
Maintainers usually reply within 1 day
-
docker-made-me-do-it enhancement
Difficulty 5/5 Over a week Newbie friendliness 20/100
Maintainers usually reply within 1 day
-
`HttpApiSchema`: stream bodies in a custom framingPossibly taken @effect-bot claimed this today. Opendocker-made-me-do-it enhancement
Effect-TS/effect#9910 · 1 assignee ·
Maintainers usually reply within 1 day
-
`HttpApiSchema` streams: declare every media type a stream can be sent asPossibly taken @effect-bot claimed this today. Opendocker-made-me-do-it enhancement
Effect-TS/effect#9909 · 1 assignee ·
Maintainers usually reply within 1 day
All issues in Effect-TS/effect
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
openremote/openremote#3487 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
coollabsio/jean#867 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
ehmpathy/rhachet-roles-bhuild#616 ·
Maintainers usually reply within 1 day
-
📘 Docs supabase/cli
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
Maintainers usually reply within 1 day
-
bug(cli): theme file home-directory check uses startsWith (sibling dirs pass, Windows casing fails)Openarea/core kind/bug priority/p2 status/bot-triaged
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
google-gemini/gemini-cli#29955 · 2 comments ·
Maintainers usually reply within 1 day