{"record":{"id":"62bed6f238f1f12b","repo":"paperclipai/paperclip","slug":"railway-rate-limited","errorCode":"railway_rate_limited","errorMessage":"Railway is rate limiting requests. Wait before trying again.","messagePattern":"Railway is rate limiting requests\\. Wait before trying again\\.","errorType":"error_code","errorClass":"RailwayError","httpStatus":null,"severity":"warning","filePath":"server/src/services/railway.ts","lineNumber":214,"sourceCode":"  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      }\n      throw new RailwayError(\"railway_api_error\", \"Railway could not complete the request. Check target IDs, resource permissions, and deployment eligibility. Inspect status before retrying a mutation.\");\n    }\n    if (!payload.data || typeof payload.data !== \"object\") throw new RailwayError(\"railway_invalid_response\", \"Railway returned no API data.\");\n    return payload.data;\n  }\n\n  async function validateTarget(args: Record<string, any>) {\n    const data = await query(RAILWAY_QUERIES.target, { projectId: args.projectId, environmentId: args.environmentId, serviceId: args.serviceId });","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/railway.ts#L196-L232","documentation":"Thrown by query() (code railway_rate_limited) when Railway's API responds with HTTP 429, meaning too many requests in a window. It instructs the caller to wait before retrying rather than hammering the endpoint.","triggerScenarios":"Polling deployment status in a tight loop; bulk project/service enumeration; multiple concurrent clients sharing one token; automated retries without backoff after a failure.","commonSituations":"Heartbeat-style pollers issuing GraphQL queries every few seconds; scripts iterating hundreds of environments; shared org token exhausted by several integrations.","solutions":["Back off and retry with exponential delay, honoring any Retry-After header","Add caching/debouncing to status polling loops","Serialize or rate-limit requests across all clients using the same token","Reduce polling frequency (e.g. seconds to minutes) for deployment status"],"exampleFix":"// before\nwhile (!done) status = await client.getDeployment(id); // 429\n// after\nawait pRetry(async () => client.getDeployment(id), {\n  minTimeout: 1000, factor: 2, maxTimeout: 30000,\n  onFailedAttempt: e => { if (e.code !== \"railway_rate_limited\") throw e; }\n});","handlingStrategy":"retry","validationCode":"// client-side rate budget check before calls\nclass RailwayThrottle {\n  constructor(maxPerMin = 60) { this.times = []; this.max = maxPerMin; }\n  async gate() {\n    const now = Date.now();\n    this.times = this.times.filter(t => now - t < 60000);\n    if (this.times.length >= this.max) {\n      await new Promise(r => setTimeout(r, 60000 - (now - this.times[0])));\n    }\n    this.times.push(Date.now());\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await client.query(document, variables);\n} catch (e) {\n  if (e?.code === \"railway_rate_limited\") {\n    await sleep(expBackoff(attempt++, 1000, 30000));\n    return client.query(document, variables);\n  } else throw e;\n}","preventionTips":["Poll deployment status with exponential backoff, not fixed tight loops","Cache read results and share one client across concurrent callers","Respect Retry-After headers when present","Alert on repeated 429s indicating runaway automation"],"tags":["railway","rate-limit","retry","http-429"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}