paperclipai/paperclip · error · RailwayError
railway_deployment_ineligible
railway_deployment_ineligible
Error message
Railway does not allow this deployment to be redeployed.
What it means
The `redeploy` Railway operation throws `railway_deployment_ineligible` (HTTP 409) when the target deployment's `canRedeploy` flag is false or missing, as fetched via `validateDeployment` against the Railway GraphQL API. Railway only permits redeploying deployments that are eligible (typically active/recent deployments on services with redeploy enabled); crashed, removed, or very old builds are ineligible. The check runs before the mutation so no partial state is created.
Solutions
- Fetch current deployments via `railway_list-deployments` and redeploy the latest eligible deployment instead of the stale one.
- Run `railway_deployment-status` for the deploymentId and verify `canRedeploy: true` before invoking redeploy.
- If the deployment is permanently ineligible, trigger a new deployment (e.g. push to the connected branch) rather than redeploying.
- Confirm the service type supports redeploy (some image/template-based services don't) and update service settings if needed.
Example fix
// before
await railwayCall("railway_redeploy", { projectId, environmentId, serviceId, deploymentId: staleId });
// after
const d = await railwayCall("railway_deployment-status", { projectId, environmentId, serviceId, deploymentId });
if (!d.canRedeploy) {
const list = await railwayCall("railway_list-deployments", { projectId, environmentId, serviceId, first: 1 });
deploymentId = list.deployments.edges[0].node.id;
}
await railwayCall("railway_redeploy", { projectId, environmentId, serviceId, deploymentId }); Defensive patterns
Strategy: validation
Validate before calling
const d = await railwayCall("railway_deployment-status", { projectId, environmentId, serviceId, deploymentId });
if (!d || d.canRedeploy !== true) {
throw new Error(`Deployment ${deploymentId} is not redeployable; select a recent eligible deployment`);
} Type guard
function canRedeploy(d) {
return typeof d === "object" && d !== null && d.canRedeploy === true && typeof d.id === "string";
} Try / catch
try {
await railwayCall("railway_redeploy", args);
} catch (e) {
if (e?.code === "railway_deployment_ineligible") {
// fall back to latest eligible deployment
const list = await railwayCall("railway_list-deployments", { projectId, environmentId, serviceId, first: 1 });
await railwayCall("railway_redeploy", { ...args, deploymentId: list.deployments.edges[0].node.id });
} else throw e;
} Prevention
- Always check `canRedeploy` via deployment-status before issuing redeploy.
- Resolve deployment IDs fresh from list-deployments rather than caching them.
- Treat redeploy as branch-push alternative only when the deployment is recent and successful.
- Watch for 409 status on this code and route to target-selection logic.
When it happens
Trigger: Calling the `railway_redeploy` tool with a `deploymentId` whose `deployment.canRedeploy` field returned false from the RAILWAY_QUERIES.deployment lookup — e.g. redeploying an already-crashed deployment, a deployment for a service whose latest snapshot no longer exists, or an ancient deployment past Railway's redeploy window.
Common situations: Agents or scripts caching an old deploymentId and replaying it days later; attempting to redeploy after a service's settings changed (e.g. image-based or template services that don't support redeploy); picking a superseded deployment from `list-deployments` instead of the latest; redeploying deployments attached to deleted environments or branches.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- railway_operation_unconfirmed
- A different semantic result was already committed
- ambiguous_personal_grant
- Cloud runtime identity is already claimed by another…
- database-contract-unmet
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/6b4cc343f6fd9866.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/railway.ts:287
if (args.startDate && args.endDate && Date.parse(args.startDate) > Date.parse(args.endDate)) throw new RailwayError("railway_invalid_arguments", "Log start time must precede end time.", 400);
const data = await query(args.kind === "build" ? RAILWAY_QUERIES.buildLogs : RAILWAY_QUERIES.runtimeLogs, { deploymentId: args.deploymentId, limit: args.limit, startDate: args.startDate, endDate: args.endDate, filter: args.filter });
const lines = data[args.kind === "build" ? "buildLogs" : "deploymentLogs"];
if (!Array.isArray(lines)) throw new RailwayError("railway_invalid_response", "Railway returned invalid log data.");
let bytes = 0;
let messageTruncated = false;
const bounded = [];
for (const line of lines.slice(0, args.limit)) {
const message = String(line.message ?? "");
if (message.length > 8192) messageTruncated = true;
const safe = redact({ timestamp: line.timestamp, severity: line.severity, message: message.slice(0, 8192) });
bytes += Buffer.byteLength(JSON.stringify(safe));
if (bytes > 64 * 1024) break;
bounded.push(safe);
}
result = { deploymentId: args.deploymentId, kind: args.kind, lines: bounded, truncated: messageTruncated || bounded.length < lines.length, limitReached: lines.length >= args.limit }; break;
}
case "redeploy":
if (!deployment?.canRedeploy) throw new RailwayError("railway_deployment_ineligible", "Railway does not allow this deployment to be redeployed.", 409);
result = await query(RAILWAY_QUERIES.redeploy, { deploymentId: args.deploymentId });
if (!id.safeParse(record(record(result).deploymentRedeploy).id).success) throw new RailwayError("railway_operation_unconfirmed", "Railway did not confirm a resulting deployment. Inspect deployment status before retrying.");
break;
case "restart":
result = await query(RAILWAY_QUERIES.restart, { deploymentId: args.deploymentId });
if (record(result).deploymentRestart !== true) throw new RailwayError("railway_operation_unconfirmed", "Railway did not confirm the restart. Inspect deployment status before retrying.");
result = { ...record(result), targetDeploymentId: args.deploymentId };
break;
case "rollback":
if (!deployment?.canRollback) throw new RailwayError("railway_deployment_ineligible", "Railway does not allow rollback to this deployment.", 409);
result = await query(RAILWAY_QUERIES.rollback, { deploymentId: args.deploymentId });
if (record(result).deploymentRollback !== true) throw new RailwayError("railway_operation_unconfirmed", "Railway did not confirm the rollback. Inspect deployment status before retrying.");
result = { ...record(result), targetDeploymentId: args.deploymentId };
break;
case "run-command":
if (!Array.isArray(deployment?.instances) || !deployment.instances.some((entry: { id: string }) => entry.id === args.deploymentInstanceId) || deployment.status !== "SUCCESS") throw new RailwayError("railway_target_mismatch", "The container instance is not part of the selected running deployment.", 403);
if (!options.runCommand) throw new RailwayError("railway_ssh_setup_required", "Configure Container access on this Railway connection before running commands.", 422);
result = await options.runCommand({ deploymentInstanceId: args.deploymentInstanceId, command: args.command, timeoutSeconds: args.timeoutSeconds, signal: options.signal }); break;View on GitHub (pinned to 3f1d897a7c)