{"record":{"id":"6ee5c9418b2a26a6","repo":"paperclipai/paperclip","slug":"railway-request-failed","errorCode":"railway_request_failed","errorMessage":"Railway could not be reached. A deployment request may have succeeded; inspect deployment status before retrying.","messagePattern":"Railway could not be reached\\. A deployment request may have succeeded; inspect deployment status before retrying\\.","errorType":"error_code","errorClass":"RailwayError","httpStatus":null,"severity":"error","filePath":"server/src/services/railway.ts","lineNumber":206,"sourceCode":"  } catch { throw new RailwayError(\"railway_workspace_discovery_failed\", \"Railway could not list authorized workspaces. Refresh actions or reconnect and select a workspace.\"); }\n  const workspaceId = Array.isArray(data.workspaces) ? data.workspaces.find((workspace) => id.safeParse(workspace?.id).success)?.id : undefined;\n  if (!workspaceId) throw new RailwayError(\"railway_workspace_required\", \"No authorized Railway workspace was found. Reconnect Railway and select a workspace to enable direct operations.\", 403);\n  return workspaceId;\n}\n\nexport function createRailwayClient(options: RailwayClientOptions) {\n  if (!/^Bearer [^\\r\\n]+$/.test(options.authorization)) throw new RailwayError(\"railway_authorization_required\", \"Reconnect Railway to authorize API access.\", 401);\n  const secret = options.authorization.slice(7);\n  const redact = (value: unknown) => JSON.parse(redactSensitiveText(JSON.stringify(value).split(secret).join(\"[REDACTED]\")));\n\n  async function query(document: string, variables: Record<string, unknown>): Promise<Record<string, any>> {\n    options.signal.throwIfAborted();\n    let response: Response;\n    try {\n      response = await options.request(RAILWAY_API_URL, { method: \"POST\", redirect: \"error\", signal: options.signal, headers: { \"content-type\": \"application/json\", Authorization: options.authorization }, body: JSON.stringify({ query: document, variables }) });\n    } catch (error) {\n      if (options.signal.aborted) throw options.signal.reason;\n      throw new RailwayError(\"railway_request_failed\", \"Railway could not be reached. A deployment request may have succeeded; inspect deployment status before retrying.\");\n    }\n    if (response.status === 401 || response.status === 403) {\n      await response.body?.cancel();\n      throw new RailwayError(\"railway_api_authorization_required\", \"Railway rejected API access. Reconnect with access to the required workspace or project. Hosted connection tokens are used only if Railway accepts them for API access.\", response.status);\n    }\n    if (!response.ok) {\n      await response.body?.cancel();\n      throw new RailwayError(response.status === 429 ? \"railway_rate_limited\" : \"railway_api_unavailable\", response.status === 429 ? \"Railway is rate limiting requests. Wait before trying again.\" : \"Railway is unavailable. Check deployment status before retrying a deployment operation.\");\n    }\n    const body = await boundedResponseText(response, options.signal);\n    let payload: Record<string, any>;\n    try { payload = record(JSON.parse(body)); }\n    catch { throw new RailwayError(\"railway_invalid_response\", \"Railway returned an invalid API response.\"); }\n    if (payload.errors) {\n      // Provider errors can echo variables, credentials or application secrets.\n      if (Array.isArray(payload.errors) && payload.errors.some((error) => [\"UNAUTHENTICATED\", \"FORBIDDEN\"].includes(error?.extensions?.code) || [\"Not Authorized\", \"Unauthorized\", \"Forbidden\"].includes(error?.message))) {\n        throw new RailwayError(\"railway_api_authorization_required\", \"Railway denied this API request. Use IDs from a workspace selected during consent, or reconnect to grant access to the required workspace.\", 403);\n      }","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/railway.ts#L188-L224","documentation":"Thrown by the internal query() helper (code railway_request_failed) when the fetch POST to the Railway GraphQL API itself rejects (network error), and the abort signal is not the cause. The message warns that a prior deployment mutation may have succeeded server-side, so callers must check deployment status before blindly retrying.","triggerScenarios":"DNS failure, connection refused/reset, TLS error, proxy blocking api.railway.com, request aborted by server shutdown mid-flight but signal check races, offline host.","commonSituations":"Server egress firewall blocks Railway's API; transient network partition during a deployment trigger; container without outbound internet; VPN dropped mid-request.","solutions":["Check network/DNS egress from the server host to api.railway.com (curl test)","Inspect deployment status in Railway before retrying any deployment mutation","Retry the request after connectivity is restored (idempotent reads only)","Verify proxy/firewall/VPN configuration allows HTTPS to Railway"],"exampleFix":"// before\nawait client.deploy({ projectId }); // throws railway_request_failed on network blip\nawait client.deploy({ projectId }); // risk: duplicate deployment\n// after\ntry {\n  await client.deploy({ projectId });\n} catch (e) {\n  if (e.code === \"railway_request_failed\") {\n    const status = await client.getDeploymentStatus({ projectId }); // check first\n    if (!status.inProgress) await client.deploy({ projectId });\n  }\n}","handlingStrategy":"retry","validationCode":"async function railwayApiReachable() {\n  try {\n    const res = await fetch(\"https://api.railway.com\", { method: \"HEAD\", signal: AbortSignal.timeout(5000) });\n    return res.status < 500;\n  } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await client.deploy(input);\n} catch (e) {\n  if (e?.code === \"railway_request_failed\") {\n    await checkDeploymentStatus(input.projectId); // may have succeeded\n    await pRetry(() => client.deploy(input), { retries: 2, minTimeout: 1000 });\n  } else throw e;\n}","preventionTips":["Allow egress to api.railway.com from the server host/firewall","Always verify deployment status after a network failure before retrying mutations","Use timeouts and idempotency checks around deployment triggers","Monitor DNS/VPN health in the deployment environment"],"tags":["railway","network","fetch","retry-safety"],"backgroundTag":"http-request-failed","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}