NodeHttpServer disposal can remain pending indefinitely when a client aborts a request whose handler is waiting on an upstream FetchHttpClient request. The configured gracefulShutdownTimeout does not bound disposal in this case.
Reproduction
https://github.com/schickling-repros/2026-07-effect-node-http-aborted-request-dispose
npm install
node repro.mjs
The reproduction starts:
- An upstream Node server that accepts a
HEAD request but intentionally leaves its response pending.
- An Effect
NodeHttpServer whose HEAD / handler awaits HttpClient.head to that upstream.
- A downstream Node client that aborts after the upstream request starts.
runtime.dispose() with gracefulShutdownTimeout: "100 millis".
Expected
The aborted request handler is interrupted and runtime.dispose() completes, bounded by the configured graceful shutdown timeout.
Actual
The request handler fiber remains in the server's handler scope and runtime.dispose() does not complete. The reproduction fails after two seconds:
Error: FAIL: runtime.dispose exceeded 2 seconds
Temporary instrumentation showed that the server's preemptive shutdown finalizer completes, but the handler child scope retains the aborted HEAD / fiber, preventing the outer server scope finalizer from running. The current response-close guard only interrupts when nodeResponse.writableEnded is false; in this case the close observed during disposal has writableEnded === true while the handler fiber is still pending.
Versions
effect: 4.0.0-beta.99
@effect/platform-node: 4.0.0-beta.99
- Node.js:
24.15.0
- Linux x86_64
The same standalone reproduction fails against current Effect main at ce95d88603e9facbcd6c462c5444e391792dde6b.
Posted on behalf of @schickling
| field |
value |
agent_name |
☁️ co3-billow |
agent_session_id |
7f3b9666-034f-4e9e-890b-491957f7f75b |
agent_tool |
Codex CLI |
agent_tool_version |
0.144.1 |
agent_runtime |
Codex CLI 0.144.1 |
agent_model |
unknown |
runtime_profile |
/nix/store/1n76sy6i9y3ki3k4w04jksqvygw67sj0-coding-agent-runtime-profile/share/coding-agents/profile.json |
skills_manifest |
/nix/store/jn6r0r7r59x3a525jch4pwzwykszqp60-agent-skills-corpus/share/agent-skills/manifest.json |
worktree |
effect-node-http-aborted-head-repro/main |
machine |
dev3 |
tooling_profile |
dotfiles@4eac376 |
NodeHttpServerdisposal can remain pending indefinitely when a client aborts a request whose handler is waiting on an upstreamFetchHttpClientrequest. The configuredgracefulShutdownTimeoutdoes not bound disposal in this case.Reproduction
https://github.com/schickling-repros/2026-07-effect-node-http-aborted-request-dispose
The reproduction starts:
HEADrequest but intentionally leaves its response pending.NodeHttpServerwhoseHEAD /handler awaitsHttpClient.headto that upstream.runtime.dispose()withgracefulShutdownTimeout: "100 millis".Expected
The aborted request handler is interrupted and
runtime.dispose()completes, bounded by the configured graceful shutdown timeout.Actual
The request handler fiber remains in the server's handler scope and
runtime.dispose()does not complete. The reproduction fails after two seconds:Temporary instrumentation showed that the server's preemptive shutdown finalizer completes, but the handler child scope retains the aborted
HEAD /fiber, preventing the outer server scope finalizer from running. The current response-close guard only interrupts whennodeResponse.writableEndedis false; in this case the close observed during disposal haswritableEnded === truewhile the handler fiber is still pending.Versions
effect:4.0.0-beta.99@effect/platform-node:4.0.0-beta.9924.15.0The same standalone reproduction fails against current Effect main at
ce95d88603e9facbcd6c462c5444e391792dde6b.Posted on behalf of @schickling
agent_nameagent_session_idagent_toolagent_tool_versionagent_runtimeagent_modelruntime_profileskills_manifestworktreemachinetooling_profile