paperclipai/paperclip · error · RailwayError

railway_output_limit

railway_output_limit

Error message

Railway's response exceeded the limit. Request fewer results or a shorter log interval.

What it means

RailwayError (code railway_output_limit) thrown by boundedResponseText when the accumulated response body exceeds 1 MiB (1024 * 1024 bytes). The library caps Railway API responses (logs, list endpoints) to protect memory, and instructs callers to narrow the request rather than growing the cap.

Solutions

  1. Narrow the request: request fewer log results or a shorter time window / coarser interval, as the message advises
  2. Paginate: use the Railway API's cursor/pagination and fetch in chunks, processing each bounded page separately
  3. Filter server-side where possible (specific deployment id, severity level) instead of downloading everything
  4. If genuinely needed, raise the 1 MiB cap in a fork/patch knowingly — the limit is a deliberate memory guard

Example fix

// before
const text = await railway.logs({ deploymentId, limit: 10000 }); // > 1MiB
// after
const text = await railway.logs({ deploymentId, limit: 500, since: oneHourAgo }); // stay under the limit; page for more
Defensive patterns

Strategy: try-catch

Validate before calling

// Cap the request server-side before reading
const params = new URLSearchParams({ limit: String(Math.min(requestedLimit, 500)) });
if (rangeMs > MAX_LOG_WINDOW_MS) throw new Error("Narrow the log window to stay under the 1MiB response limit");

Type guard

null

Try / catch

try {
  const text = await boundedResponseText(res, signal);
} catch (e) {
  if (e.code === "railway_output_limit") {
    logger.warn("Railway response exceeded 1MiB; retrying with a smaller window");
    return fetchRailwayLogs({ ...opts, limit: Math.floor(opts.limit / 4), since: narrowerWindow });
  }
  throw e;
}

Prevention

When it happens

Trigger: Streaming a Railway deployment log tail or large list response whose byte count passes 1048576 while reading chunks — e.g. requesting too many log entries, too wide a time window, or verbose services emitting megabytes of output.

Common situations: Fetching full historical deployment logs for a chatty service; listing hundreds of environments/plugins in one call; log interval set too fine (per-line streaming over hours); a service stuck in a crash-loop generating enormous logs.

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/75dbc3a02ef54024. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/railway.ts:157

  rollback: `mutation PaperclipRailwayRollback($deploymentId:String!) { deploymentRollback(id:$deploymentId) }`,
};

function record(value: unknown): Record<string, any> {
  return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, any> : {};
}

async function boundedResponseText(response: Response, signal: AbortSignal): Promise<string> {
  const reader = response.body?.getReader();
  if (!reader) throw new RailwayError("railway_invalid_response", "Railway returned an empty response.");
  const chunks: Uint8Array[] = [];
  let size = 0;
  try {
    for (;;) {
      signal.throwIfAborted();
      const { done, value } = await reader.read();
      if (done) break;
      size += value.byteLength;
      if (size > 1024 * 1024) throw new RailwayError("railway_output_limit", "Railway's response exceeded the limit. Request fewer results or a shorter log interval.");
      chunks.push(value);
    }
  } finally { await reader.cancel().catch(() => {}); }
  return Buffer.concat(chunks).toString("utf8");
}

/** Discover a consented workspace without requesting account-wide API access. */
export async function discoverRailwayWorkspace(options: RailwayClientOptions): Promise<string> {
  const send = (init: RequestInit) => options.request(RAILWAY_MCP_URL, { ...init, redirect: "error", signal: options.signal });
  const headers = { Authorization: options.authorization };
  const list = (requestHeaders: Record<string, string>) => send({
    method: "POST", headers: mcpHttpRequestHeaders(requestHeaders),
    body: JSON.stringify({ jsonrpc: "2.0", id: "paperclip-railway-workspace-probe", method: "tools/call", params: { name: "list-workspaces", arguments: {} } }),
  });
  let response = await list(headers);
  if (response.status === 400) {
    await response.body?.cancel();
    response = await list(await initializeMcpHttpSession({ send, headers, requestId: "paperclip-railway-workspace-probe" }));

View on GitHub (pinned to 3f1d897a7c)