paperclipai/paperclip · error · RailwayError

railway_request_failed

railway_request_failed

Error message

Railway could not be reached. A deployment request may have succeeded; inspect deployment status before retrying.

What it means

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.

Solutions

  1. Check network/DNS egress from the server host to api.railway.com (curl test)
  2. Inspect deployment status in Railway before retrying any deployment mutation
  3. Retry the request after connectivity is restored (idempotent reads only)
  4. Verify proxy/firewall/VPN configuration allows HTTPS to Railway

Example fix

// before
await client.deploy({ projectId }); // throws railway_request_failed on network blip
await client.deploy({ projectId }); // risk: duplicate deployment
// after
try {
  await client.deploy({ projectId });
} catch (e) {
  if (e.code === "railway_request_failed") {
    const status = await client.getDeploymentStatus({ projectId }); // check first
    if (!status.inProgress) await client.deploy({ projectId });
  }
}
Defensive patterns

Strategy: retry

Validate before calling

async function railwayApiReachable() {
  try {
    const res = await fetch("https://api.railway.com", { method: "HEAD", signal: AbortSignal.timeout(5000) });
    return res.status < 500;
  } catch { return false; }
}

Try / catch

try {
  await client.deploy(input);
} catch (e) {
  if (e?.code === "railway_request_failed") {
    await checkDeploymentStatus(input.projectId); // may have succeeded
    await pRetry(() => client.deploy(input), { retries: 2, minTimeout: 1000 });
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: Server egress firewall blocks Railway's API; transient network partition during a deployment trigger; container without outbound internet; VPN dropped mid-request.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/6ee5c9418b2a26a6. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/railway.ts:206

  } catch { throw new RailwayError("railway_workspace_discovery_failed", "Railway could not list authorized workspaces. Refresh actions or reconnect and select a workspace."); }
  const workspaceId = Array.isArray(data.workspaces) ? data.workspaces.find((workspace) => id.safeParse(workspace?.id).success)?.id : undefined;
  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);
  return workspaceId;
}

export function createRailwayClient(options: RailwayClientOptions) {
  if (!/^Bearer [^\r\n]+$/.test(options.authorization)) throw new RailwayError("railway_authorization_required", "Reconnect Railway to authorize API access.", 401);
  const secret = options.authorization.slice(7);
  const redact = (value: unknown) => JSON.parse(redactSensitiveText(JSON.stringify(value).split(secret).join("[REDACTED]")));

  async function query(document: string, variables: Record<string, unknown>): Promise<Record<string, any>> {
    options.signal.throwIfAborted();
    let response: Response;
    try {
      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 }) });
    } catch (error) {
      if (options.signal.aborted) throw options.signal.reason;
      throw new RailwayError("railway_request_failed", "Railway could not be reached. A deployment request may have succeeded; inspect deployment status before retrying.");
    }
    if (response.status === 401 || response.status === 403) {
      await response.body?.cancel();
      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);
    }
    if (!response.ok) {
      await response.body?.cancel();
      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.");
    }
    const body = await boundedResponseText(response, options.signal);
    let payload: Record<string, any>;
    try { payload = record(JSON.parse(body)); }
    catch { throw new RailwayError("railway_invalid_response", "Railway returned an invalid API response."); }
    if (payload.errors) {
      // Provider errors can echo variables, credentials or application secrets.
      if (Array.isArray(payload.errors) && payload.errors.some((error) => ["UNAUTHENTICATED", "FORBIDDEN"].includes(error?.extensions?.code) || ["Not Authorized", "Unauthorized", "Forbidden"].includes(error?.message))) {
        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);
      }

View on GitHub (pinned to 3f1d897a7c)