{"record":{"id":"cd0fc0bdaf18690c","repo":"paperclipai/paperclip","slug":"railway-api-error","errorCode":"railway_api_error","errorMessage":"Railway could not complete the request. Check target IDs, resource permissions, and deployment eligibility. Inspect status before retrying a mutation.","messagePattern":"Railway could not complete the request\\. Check target IDs, resource permissions, and deployment eligibility\\. Inspect status before retrying a mutation\\.","errorType":"error_code","errorClass":"RailwayError","httpStatus":null,"severity":"error","filePath":"server/src/services/railway.ts","lineNumber":225,"sourceCode":"    }\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 });\n    if (data.project?.id !== args.projectId || data.environment?.id !== args.environmentId || data.environment?.projectId !== args.projectId || data.service?.id !== args.serviceId || data.service?.projectId !== args.projectId || data.serviceInstance?.environmentId !== args.environmentId || data.serviceInstance?.serviceId !== args.serviceId) {\n      throw new RailwayError(\"railway_target_mismatch\", \"The service and environment do not belong to the selected Railway project.\", 403);\n    }\n    return data.serviceInstance;\n  }\n\n  async function validateDeployment(args: Record<string, any>) {\n    const data = await query(RAILWAY_QUERIES.deployment, { deploymentId: args.deploymentId });\n    const d = record(data.deployment);\n    if (d.id !== args.deploymentId || d.projectId !== args.projectId || d.environmentId !== args.environmentId || d.serviceId !== args.serviceId) throw new RailwayError(\"railway_target_mismatch\", \"The deployment does not belong to the selected Railway target.\", 403);\n    return d;","sourceCodeStart":207,"sourceCodeEnd":243,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/railway.ts#L207-L243","documentation":"This client wraps Railway's GraphQL API (backboard.railway.com). After an HTTP 200 response, if the payload contains a top-level `errors` array whose entries are not recognized as auth-denial codes (UNAUTHENTICATED/FORBIDDEN/Not Authorized/Unauthorized/Forbidden), the client throws `railway_api_error`. It is a catch-all for provider-side GraphQL execution failures: bad IDs, resources the token can't see, or mutations Railway refuses (e.g. deployment not eligible). The generic message intentionally avoids echoing error details because provider errors can leak variables and secrets.","triggerScenarios":"Calling any operation (list-projects, service-status, redeploy, etc.) when Railway returns `errors` with codes/messages outside the auth-denial allowlist — e.g. a projectId that doesn't exist, a deleted service, an invalid pagination cursor, or a mutation rejected for deployment-state reasons.","commonSituations":"Stale or hand-copied UUIDs pointing at deleted Railway resources; cross-workspace IDs used with a token scoped to a different workspace; a redeploy/rollback attempted on a deployment Railway considers ineligible; Railway schema or error-message changes that no longer match the auth allowlist.","solutions":["Re-run the corresponding status/read operation (service-status, deployment-status, list-deployments) to confirm every ID exists and belongs to the consented workspace","Verify each UUID (projectId, environmentId, serviceId, deploymentId) was obtained from a list-projects/list-services/list-environments call made with the same token","If retrying a mutation (redeploy/restart/rollback), check `canRedeploy`/`canRollback` on the deployment first and inspect current status before retrying","If IDs are confirmed valid, reconnect the Railway connection to refresh workspace/project access, then retry"],"exampleFix":"// before: blind mutation retry\nawait client.call(\"paperclip-railway-redeploy\", args);\n// after: inspect status first\nconst d = await client.call(\"paperclip-railway-deployment-status\", args);\nif (!d.canRedeploy) throw new Error(\"deployment not redeployable\");\nawait client.call(\"paperclip-railway-redeploy\", args);","handlingStrategy":"try-catch","validationCode":"const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst ok = UUID.test(args.projectId) && UUID.test(args.environmentId) && UUID.test(args.serviceId);","typeGuard":"function isRailwayError(e: unknown): e is RailwayError {\n  return e instanceof RailwayError && typeof (e as RailwayError).code === \"string\";\n}","tryCatchPattern":"try {\n  return await client.call(name, args);\n} catch (e) {\n  if (isRailwayError(e) && e.code === \"railway_api_error\") {\n    const status = await client.call(statusOpFor(name), args); // inspect before retry\n    return retryOnceOrThrow(status);\n  }\n  throw e;\n}","preventionTips":["Always source IDs from prior list/status calls with the same connection, never from memory or other workspaces","Check canRedeploy/canRollback before destructive mutations","Treat mutations as maybe-applied: inspect status before any retry","Keep the connection's workspace consent in sync with the projects you target"],"tags":["graphql","railway","api","upstream"],"backgroundTag":"api-error-response","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"}