Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

RpcServer: with disableFatalDefects, a handler that throws before returning its Effect still fails every pending call

Open
#9,892 0 comments 0 reactions 1 assignee View on GitHub

Maintainers usually reply within 1 day

@effect-bot is already working on this.

Since Oct 24, 2026.

  • #9899 by @effect-bot — open

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from Effect-TS/effect

All issues in Effect-TS/effect

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.