-
-
Notifications
You must be signed in to change notification settings - Fork 490
Description
What version of Effect is running?
- effect: 3.19.13
- @effect/platform: 0.94.0
- @effect/platform-node: 0.104.0
What steps can reproduce the bug?
The bug occurs when causeResponse receives a cause tree where a non-client-abort interrupt appears before clientAbortFiberId in traversal order:
const parentFiberId = yield* Effect.fiberId
const childInterrupt = Cause.interrupt(parentFiberId)
const clientAbortInterrupt = Cause.interrupt(HttpServerError.clientAbortFiberId)
const cause = Cause.sequential(childInterrupt, clientAbortInterrupt)
const [response] = yield* HttpServerError.causeResponse(cause)
// response.status is 503 (bug) instead of 499 (expected)What is the expected behavior?
The server should return 499 (Client Closed Request) because clientAbortFiberId is present in the cause's interruptors.
What do you see instead?
The server returns 503 (Service Unavailable) because causeResponse only checks the first interrupt node encountered during Cause.reduce traversal.
Root Cause Analysis
The issue is in causeResponse which checks cause.fiberId === clientAbortFiberId on each interrupt node during traversal:
case "Interrupt": {
if (acc[1]._tag !== "Empty") {
return Option.none()
}
const response = cause.fiberId === clientAbortFiberId ? clientAbortError : serverAbortError
return Option.some([Effect.succeed(response), cause] as const)
}Cause.reduce uses left-first traversal for Sequential nodes. If another interrupt appears before clientAbortFiberId in the cause tree, the function returns 503 even though the client abort is present.
Fix
Pre-compute whether clientAbortFiberId exists anywhere in the cause tree before traversing:
const isClientAbort = HashSet.has(Cause.interruptors(cause), clientAbortFiberId)
// ... then use isClientAbort instead of checking individual node's fiberIdNote on Testing
Effect's runtime properly propagates interrupt IDs to child fibers. When a parent fiber is interrupted with clientAbortFiberId, child fibers also receive that same ID. This means real HTTP tests with Effect.all or nested fibers don't easily reproduce the problematic cause structure.
The bug manifests with specific cause tree structures that can occur in edge cases or be constructed synthetically for testing.
Additional information
This issue is particularly problematic in proxy/gateway scenarios where 503 suggests server unavailability and can trigger incorrect retry logic and alerting, while 499 correctly indicates the client closed the connection.