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
- 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
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
- 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
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
- CreateOS connection failed.
- GitHub webhook configuration could not be confirmed…
- network
- railway_workspace_discovery_failed
- Announcement request failed
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)