{"record":{"id":"75dbc3a02ef54024","repo":"paperclipai/paperclip","slug":"railway-output-limit","errorCode":"railway_output_limit","errorMessage":"Railway's response exceeded the limit. Request fewer results or a shorter log interval.","messagePattern":"Railway's response exceeded the limit\\. Request fewer results or a shorter log interval\\.","errorType":"error_code","errorClass":"RailwayError","httpStatus":null,"severity":"error","filePath":"server/src/services/railway.ts","lineNumber":157,"sourceCode":"  rollback: `mutation PaperclipRailwayRollback($deploymentId:String!) { deploymentRollback(id:$deploymentId) }`,\n};\n\nfunction record(value: unknown): Record<string, any> {\n  return value && typeof value === \"object\" && !Array.isArray(value) ? value as Record<string, any> : {};\n}\n\nasync function boundedResponseText(response: Response, signal: AbortSignal): Promise<string> {\n  const reader = response.body?.getReader();\n  if (!reader) throw new RailwayError(\"railway_invalid_response\", \"Railway returned an empty response.\");\n  const chunks: Uint8Array[] = [];\n  let size = 0;\n  try {\n    for (;;) {\n      signal.throwIfAborted();\n      const { done, value } = await reader.read();\n      if (done) break;\n      size += value.byteLength;\n      if (size > 1024 * 1024) throw new RailwayError(\"railway_output_limit\", \"Railway's response exceeded the limit. Request fewer results or a shorter log interval.\");\n      chunks.push(value);\n    }\n  } finally { await reader.cancel().catch(() => {}); }\n  return Buffer.concat(chunks).toString(\"utf8\");\n}\n\n/** Discover a consented workspace without requesting account-wide API access. */\nexport async function discoverRailwayWorkspace(options: RailwayClientOptions): Promise<string> {\n  const send = (init: RequestInit) => options.request(RAILWAY_MCP_URL, { ...init, redirect: \"error\", signal: options.signal });\n  const headers = { Authorization: options.authorization };\n  const list = (requestHeaders: Record<string, string>) => send({\n    method: \"POST\", headers: mcpHttpRequestHeaders(requestHeaders),\n    body: JSON.stringify({ jsonrpc: \"2.0\", id: \"paperclip-railway-workspace-probe\", method: \"tools/call\", params: { name: \"list-workspaces\", arguments: {} } }),\n  });\n  let response = await list(headers);\n  if (response.status === 400) {\n    await response.body?.cancel();\n    response = await list(await initializeMcpHttpSession({ send, headers, requestId: \"paperclip-railway-workspace-probe\" }));","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/railway.ts#L139-L175","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Narrow the request: request fewer log results or a shorter time window / coarser interval, as the message advises","Paginate: use the Railway API's cursor/pagination and fetch in chunks, processing each bounded page separately","Filter server-side where possible (specific deployment id, severity level) instead of downloading everything","If genuinely needed, raise the 1 MiB cap in a fork/patch knowingly — the limit is a deliberate memory guard"],"exampleFix":"// before\nconst text = await railway.logs({ deploymentId, limit: 10000 }); // > 1MiB\n// after\nconst text = await railway.logs({ deploymentId, limit: 500, since: oneHourAgo }); // stay under the limit; page for more","handlingStrategy":"try-catch","validationCode":"// Cap the request server-side before reading\nconst params = new URLSearchParams({ limit: String(Math.min(requestedLimit, 500)) });\nif (rangeMs > MAX_LOG_WINDOW_MS) throw new Error(\"Narrow the log window to stay under the 1MiB response limit\");","typeGuard":"null","tryCatchPattern":"try {\n  const text = await boundedResponseText(res, signal);\n} catch (e) {\n  if (e.code === \"railway_output_limit\") {\n    logger.warn(\"Railway response exceeded 1MiB; retrying with a smaller window\");\n    return fetchRailwayLogs({ ...opts, limit: Math.floor(opts.limit / 4), since: narrowerWindow });\n  }\n  throw e;\n}","preventionTips":["Request bounded result sets (small limits, short time windows) from Railway list/log endpoints","Paginate and process chunks instead of one large pull","Filter logs server-side (deployment id, severity) to shrink payloads","Expect ~1MiB as the hard cap; never design flows that need full unbounded log downloads in one call"],"tags":["limits","logging","http","railway"],"backgroundTag":"payload-too-large","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}