paperclipai/paperclip · error · RailwayError
railway_ssh_setup_required
railway_ssh_setup_required
Error message
Configure Container access on this Railway connection before running commands.
What it means
RailwayError with code railway_ssh_setup_required (HTTP 422) thrown when the run-command operation passes the target check but the Railway connection was created without a runCommand capability. Executing commands inside a Railway container requires Container access (SSH/exec) configured on the connection credentials; without it the tool cannot perform the operation and refuses with 422 instead of failing at exec time.
Solutions
- Open the Railway connection settings and enable/configure Container access (SSH/exec credentials), then reconnect the tool
- Verify the connection factory that builds options actually passes a runCommand executor for this connection type
- Re-authorize the Railway connection if Container-access credentials were revoked or rotated
- As a workaround, use redeploy/restart/rollback or read-logs, which do not require Container access
Example fix
// before (connection options)
const options = { token, redact }; // no runCommand
// after
const options = { token, redact, runCommand: createRailwayRunCommand({ sshCredentials }), signal };
await railway.call('railway_run-command', { deploymentId, deploymentInstanceId, command: 'ls' }); Defensive patterns
Strategy: validation
Validate before calling
if (typeof railwayOptions.runCommand !== 'function') {
throw new Error('Railway connection lacks Container access; configure SSH/exec credentials before run-command');
} Type guard
function hasContainerAccess(options) {
return typeof options === 'object' && options !== null && typeof options.runCommand === 'function';
} Try / catch
try {
result = await railway.call('railway_run-command', { deploymentId, deploymentInstanceId, command });
} catch (e) {
if (e?.code === 'railway_ssh_setup_required') {
// surface setup instructions to the operator or fall back to redeploy/read-logs
} else throw e;
} Prevention
- Configure Container access when creating the Railway connection, not at first use
- After credential rotation, re-create the connection so runCommand is rewired
- Feature-check options.runCommand before offering run-command to agents
- Fall back to non-exec operations (restart, read-logs) when exec is unavailable
When it happens
Trigger: Calling the railway run-command tool on a connection whose options.runCommand is undefined — i.e. the connection was configured without Container access/SSH credentials, or the connection factory did not wire the runCommand executor.
Common situations: Fresh Railway integration where only the API token was configured; connection created before Container-access feature was enabled; credentials expired or were rotated and the exec capability was dropped during reconnect; run-command used on a read-only connection.
Understand the failure class
Background: "X is required", "must be set", "cannot be empty": the missing-required-config error family, from Vertex AI project/location to WeChat keys — this error's family across 18 libraries.
Related errors
- railway_api_not_verified
- railway_ssh_host_key_invalid
- railway_ssh_key_invalid
- railway_ssh_unavailable
- railway_target_mismatch
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/9ceabe5a0107eea5.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/railway.ts:304
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;
}
}
return redact(result ?? null);
},
};
}
View on GitHub (pinned to 3f1d897a7c)