tRPC Streams Unexpectedly Closing on Railway

exkazuu
HOBBY

2 months ago

We are migrating our service from Fly.io to Railway for better reliability.

Our backend is a proxy for multiple LLMs, built with Bun, Hono, and tRPC (specifically using httpBatchStreamLink; https://trpc.io/docs/client/links/httpBatchStreamLink).

We've discovered an issue where streams to our client apps are unexpectedly closing. This problem only occurs on our Railway deployment; the exact same code works perfectly on Fly.io.

We're eager to complete this migration. Are there any known issues, platform-specific configurations, or workarounds for this kind of streaming setup on Railway? We can provide a minimal reproducible repository (without LLMs) if needed.

Note that we disabled Serveless on the experiment while we enabled it now.

Please see the error log below:

[12:43:29.722] ERROR: onError(tRPC): {"error":{"cause":{},"code":"BAD_REQUEST","name":"TRPCError"},"path":"chatStream","ctx":{"req":{},"env":{}},"type":"mutation","req":{}}
{
  error: 31 | var TRPCError = class extends Error {
         32 | 	constructor(opts) {
         33 | 		var _ref, _opts$message, _this$cause;
         34 | 		const cause = getCauseFromUnknown(opts.cause);
         35 | 		const message = (_ref = (_opts$message = opts.message) !== null && _opts$message !== void 0 ? _opts$message : cause === null || cause === void 0 ? void 0 : cause.message) !== null && _ref !== void 0 ? _ref : opts.code;
         36 | 		super(message, { cause });
                ^
TRPCError: The connection was closed.
 cause: [DOMException ...],
  code: "BAD_REQUEST"
      at new TRPCError (/app/node_modules/@trpc/server/dist/tracked-Bp72jHif.mjs:36:3)
      at <anonymous> (/app/node_modules/@trpc/server/dist/resolveResponse-Hga1xOO1.mjs:53:11)
,
  path: "chatStream",
  input: undefined,
  ctx: {
    req: Request (0 KB) {
      method: "POST",
      url: "http://XXXXXX/api/trpc/chatStream?batch=1",
      headers: Headers {
        "host": "XXXXXX",
        "user-agent": "Bun/1.2.20",
        "content-length": "54604",
        "accept": "*/*",
        "accept-encoding": "gzip, deflate, br, zstd",
        "content-type": "application/json",
        "trpc-accept": "application/jsonl",
        "x-forwarded-for": "XXXXXX",
        "x-forwarded-host": "XXXXXX",
        "x-forwarded-proto": "https",
        "x-railway-edge": "railway/asia-southeast1-eqsg3a",
        "x-railway-request-id": "PkHMtCQNTIK8wzXOacI7Nw",
        "x-real-ip": "XXXXXX",
        "x-request-start": "1755661470147",
      }
    },
    env: HTTPServer {
      address: [Object ...],
      development: false,
      fetch: [Function: fetch],
      hostname: "0.0.0.0",
      id: "",
      pendingRequests: 0,
      pendingWebSockets: 0,
      port: 8080,
      protocol: "http",
      publish: [Function: publish],
      ref: [Function: ref],
      reload: [Function: reload],
      requestIP: [Function: requestIP],
      stop: [Function: stop],
      subscriberCount: [Function: subscriberCount],
      timeout: [Function: timeout],
      unref: [Function: unref],
      upgrade: [Function: upgrade],
      url: [URL ...],
      [Symbol(Symbol.dispose)]: [Function: dispose],
    },
  },
  type: "mutation",
  req: Request (0 KB) {
    method: "POST",
    url: "http://XXXXXX/api/trpc/chatStream?batch=1",
    headers: Headers {
      "host": "XXXXXX",
      "user-agent": "Bun/1.2.20",
      "content-length": "54604",
      "accept": "*/*",
      "accept-encoding": "gzip, deflate, br, zstd",
      "content-type": "application/json",
      "trpc-accept": "application/jsonl",
      "x-forwarded-for": "XXXXXX",
      "x-forwarded-host": "XXXXXX",
      "x-forwarded-proto": "https",
      "x-railway-edge": "railway/asia-southeast1-eqsg3a",
      "x-railway-request-id": "PkHMtCQNTIK8wzXOacI7Nw",
      "x-real-ip": "XXXXXX",
      "x-request-start": "1755661470147",
    }
  },
}
Solved

8 Replies

How long would you say the stream terminates after, we have a 15 minute websocket timeout for Railway generated domains.


Status changed to Awaiting User Response Railway 2 months ago


exkazuu
HOBBY

2 months ago

Okay, it seems to be exceeding the 15-minute limit. We'll measure the exact duration to confirm. If we set up a custom domain, will that remove the 15-minute limitation?


Status changed to Awaiting Railway Response Railway 2 months ago


exkazuu
HOBBY

2 months ago

Hmm, but, https://trpc.io/docs/client/links/httpBatchStreamLink seems not to use Websocket.


exkazuu

Okay, it seems to be exceeding the 15-minute limit. We'll measure the exact duration to confirm. If we set up a custom domain, will that remove the 15-minute limitation?

Yes.


Status changed to Awaiting User Response Railway 2 months ago


exkazuu
HOBBY

2 months ago

Great! Anyway, We'll test our server with a custom domain and report the result.


Status changed to Awaiting Railway Response Railway 2 months ago


I have just been informed that the limit isn't a WS specific throttle. Can you walk us through how long the connection lasts?


Status changed to Awaiting User Response Railway 2 months ago


angelo-railway

I have just been informed that the limit isn't a WS specific throttle. Can you walk us through how long the connection lasts?

exkazuu
HOBBY

2 months ago

Okay. We cannot analyze the durations from the current logs. We'll improve our logger to measure the durations. Btw, I noticed that the error occurs both on non-streaming and streaming.


Status changed to Awaiting Railway Response Railway 2 months ago


Yea, from the network request on our side we got a 200.

From the engineering team: "The request ID they gave was for a POST request that returned a 200 in 20 ms with ~50kb, so all is good on our end."


Status changed to Awaiting User Response Railway 2 months ago


Railway
BOT

2 months ago

This thread has been marked as solved automatically due to a lack of recent activity. Please re-open this thread or create a new one if you require further assistance. Thank you!

Status changed to Solved Railway about 2 months ago