Skip to content

[Bug]: Node server crashes with unhandled EPIPE when a client disconnects during an auth error response #4410

Description

@eimexdev

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Start the nightly headless server:

    npx -y t3@nightly serve
  2. Connect a T3 Code client.

  3. Have the client present a stale or otherwise invalid session token. The server repeatedly logs:

    Rejected authenticated session credential.
      reason: Invalid session token signature.
    
  4. Close or interrupt the client connection while the resulting JSON 401 response is being written.

  5. The server emits an unhandled EPIPE and terminates.

This was captured from a real incident. It was not intentionally repeated because reproducing it terminates the server and may interrupt active provider work.

A focused deterministic regression test could:

  1. Start NodeHttpServer.layerTest in a child process.
  2. Expose a route returning a small schemaJson response.
  3. Destroy the client socket after sending the request but before the response finishes.
  4. Assert that the request fails locally while the server process remains alive.

Expected behavior

A disconnected client should terminate only that request. An invalid session credential should return a 401, or fail harmlessly if the client has already disconnected.

The T3 server must remain alive, preserving other clients and in-flight provider work.

Actual behavior

The socket emits an unhandled EPIPE, which terminates the entire Node process. All clients lose access, the server stops listening, and a manual restart is required.

Impact

Blocks work completely

This can disconnect every client and leave in-flight or pending work in a stale state.

Version or commit

  • T3 Code 0.0.29-nightly.20260723.887
  • Release commit 979854895cc539269213915cd6581cf444089bdc
  • @effect/platform-node 4.0.0-beta.78
  • effect 4.0.0-beta.78

The relevant dependency and response path remain unchanged on the inspected main checkout.

Environment

  • Linux x86_64
  • Node.js v24.18.0
  • T3 Code desktop client

Logs or stack traces

Rejected authenticated session credential.
  reason: Invalid session token signature.

node:events:487
      throw er; // Unhandled 'error' event
      ^

Error: write EPIPE
    at afterWriteDispatched (node:internal/stream_base_commons:159:15)
    at writevGeneric (node:internal/stream_base_commons:142:3)
    at Socket._writeGeneric (node:net:1024:11)
    at Socket._writev (node:net:1033:8)
    at doWrite (node:internal/streams/writable:594:12)
    at clearBuffer (node:internal/streams/writable:773:5)
    at Writable.uncork (node:internal/streams/writable:529:7)
    at ServerResponse.end (node:_http_outgoing:1095:19)
    at handleResponse (@effect/platform-node/dist/NodeHttpServer.js:377:24)
    at effect/dist/unstable/http/HttpEffect.js:54:32
Emitted 'error' event on Socket instance at:
    at emitErrorNT (node:internal/streams/destroy:170:8)
    at emitErrorCloseNT (node:internal/streams/destroy:129:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
  errno: -32,
  code: 'EPIPE',
  syscall: 'write'
}

Node.js v24.18.0
free(): invalid pointer
Aborted

Root-cause analysis

This appears to be the failure chain:

  1. T3 maps an invalid session credential to EnvironmentAuthInvalidError.

  2. That error is serialized as a schema-backed JSON 401.

  3. Effect encodes schema JSON as a Uint8Array.

  4. For Uint8Array bodies smaller than 1 MiB, @effect/platform-node calls:

    if (body.body.length < 1024 * 1024) {
      nodeResponse.end(body.body);
      return Effect.void;
    }

    This path does not wait for completion or attach an error handler.

  5. If the peer closes during the write, Node emits EPIPE on the socket. With no listener, it becomes an unhandled event and terminates the process.

The later free(): invalid pointer occurs during shutdown and may be a secondary native-cleanup failure. The first actionable failure is the unhandled socket EPIPE.

Relevant source:

Suggested fix direction

Handle completion and socket errors for small Uint8Array responses just as carefully as streamed and large responses. An EPIPE or ECONNRESET caused by a client disconnect should become a request-scoped failure or interruption, never an unhandled process event.

This may need an upstream Effect fix, a temporary dependency patch in T3 Code, or both. A regression test should explicitly destroy the client connection during a small response and verify that the server remains alive.

Workaround

There is no reliable preventative workaround because any client can disconnect during a response. Restarting the server restores access, and removing or re-pairing the stale client may reduce this particular trigger, but neither addresses the underlying crash condition.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions