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

  1. Open the Railway connection settings and enable/configure Container access (SSH/exec credentials), then reconnect the tool
  2. Verify the connection factory that builds options actually passes a runCommand executor for this connection type
  3. Re-authorize the Railway connection if Container-access credentials were revoked or rotated
  4. 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

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


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)