{"record":{"id":"0d6cbd8095745bf9","repo":"paperclipai/paperclip","slug":"railway-invalid-response","errorCode":"railway_invalid_response","errorMessage":"Railway returned an empty response.","messagePattern":"Railway returned an empty response\\.","errorType":"error_code","errorClass":"RailwayError","httpStatus":null,"severity":"error","filePath":"server/src/services/railway.ts","lineNumber":148,"sourceCode":"  services: `query PaperclipRailwayServices($projectId:String!,$first:Int!,$after:String) { project(id:$projectId) { id services(first:$first,after:$after) { edges { node { id name projectId } } ${pageInfo} } } }`,\n  environments: `query PaperclipRailwayEnvironments($projectId:String!,$first:Int!,$after:String) { project(id:$projectId) { id environments(first:$first,after:$after) { edges { node { id name projectId } } ${pageInfo} } } }`,\n  target: `query PaperclipRailwayTarget($projectId:String!,$environmentId:String!,$serviceId:String!) { project(id:$projectId) { id } environment(id:$environmentId) { id projectId } service(id:$serviceId) { id projectId } serviceInstance(environmentId:$environmentId,serviceId:$serviceId) { ${instanceFields} } }`,\n  deployment: `query PaperclipRailwayDeployment($deploymentId:String!) { deployment(id:$deploymentId) { ${deploymentFields} instances { id } } }`,\n  deployments: `query PaperclipRailwayDeployments($input:DeploymentListInput!,$first:Int!,$after:String) { deployments(input:$input,first:$first,after:$after) { edges { node { ${deploymentFields} } } ${pageInfo} } }`,\n  buildLogs: `query PaperclipRailwayBuildLogs($deploymentId:String!,$limit:Int!,$startDate:DateTime,$endDate:DateTime,$filter:String) { buildLogs(deploymentId:$deploymentId,limit:$limit,startDate:$startDate,endDate:$endDate,filter:$filter) { timestamp message severity } }`,\n  runtimeLogs: `query PaperclipRailwayRuntimeLogs($deploymentId:String!,$limit:Int!,$startDate:DateTime,$endDate:DateTime,$filter:String) { deploymentLogs(deploymentId:$deploymentId,limit:$limit,startDate:$startDate,endDate:$endDate,filter:$filter) { timestamp message severity } }`,\n  redeploy: `mutation PaperclipRailwayRedeploy($deploymentId:String!) { deploymentRedeploy(id:$deploymentId,usePreviousImageTag:true) { id status } }`,\n  restart: `mutation PaperclipRailwayRestart($deploymentId:String!) { deploymentRestart(id:$deploymentId) }`,\n  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 });","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/railway.ts#L130-L166","documentation":"RailwayError (code railway_invalid_response) thrown by boundedResponseText when the HTTP Response has no readable body stream (response.body?.getReader() is undefined). The library needs a streaming body to read and bound Railway API responses; without one it cannot proceed, so it throws this client-side error (no HTTP status appended).","triggerScenarios":"Calling a Railway API wrapper whose fetch returned a response with a null body — e.g. a 204 No Content, a HEAD request, a response already consumed by another reader, or a runtime/polyfill (older undici, service-worker intercepted fetch) that does not expose body streams.","commonSituations":"Hitting an endpoint that legitimately returns an empty body (204) through the generic body() helper; a proxy/gateway stripping the body; double-reading the response (body already consumed elsewhere); Node < 18 or polyfilled fetch without streaming support; tests stubbing fetch with { ok: true } and no body.","solutions":["Check the response.status first and handle 204/empty bodies before calling body()/boundedResponseText","Ensure the response is read exactly once — do not call response.json()/text() before boundedResponseText","Use Node >= 18 with native fetch (undici) so response.body exposes getReader()","In tests/mocks, stub fetch with a real ReadableStream body instead of a bare Response-like object"],"exampleFix":"// before\nconst text = await boundedResponseText(res, signal);\n// after\nif (res.status === 204 || !res.body) {\n  return \"\"; // nothing to read\n}\nconst text = await boundedResponseText(res, signal);","handlingStrategy":"type-guard","validationCode":"function hasReadableBody(res) {\n  return res != null && typeof res.body?.getReader === \"function\";\n}\nif (res.status === 204 || !hasReadableBody(res)) return \"\"; // empty is acceptable","typeGuard":"function isReadableResponse(res) {\n  return typeof res?.body?.getReader === \"function\";\n}","tryCatchPattern":"try {\n  const text = await boundedResponseText(res, signal);\n} catch (e) {\n  if (e.code === \"railway_invalid_response\" && !res.body) {\n    logger.warn({ status: res.status }, \"Railway returned no body; treating as empty\");\n    return \"\";\n  }\n  throw e;\n}","preventionTips":["Handle 204/HEAD/empty-body responses before invoking the body reader","Read the response exactly once — never consume body via .json()/.text() first","Require Node >= 18 native fetch so response.body streams are available","In tests, mock fetch with a real Response containing a ReadableStream body"],"tags":["http","response-parsing","railway"],"backgroundTag":"empty-response-body","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"}