paperclipai/paperclip · error · ToolGatewayHttpError
railway_api_not_verified
railway_api_not_verified
Error message
Railway API access is not verified. Refresh actions or reconnect this Railway connection.
What it means
Before executing a Railway-namespaced tool (RAILWAY_TOOL_PREFIX, e.g. railway_run-command) whose endpoint looks like Railway, the gateway verifies the connection is a Railway connection whose railwayApiStatus is exactly "available". If the stored config is not a Railway connection or its API status is anything else (unverified, error, etc.), it throws this 422 ToolGatewayHttpError with reason code railway_api_not_verified. This guards against executing Railway GraphQL/SSH calls with unverified credentials.
Solutions
- Run the connection's 'refresh actions' / Railway API verification flow so railwayApiStatus becomes "available".
- Reconnect the Railway connection with a valid Railway API token, then re-verify.
- Inspect connection.config.railwayApiStatus in the DB/UI; if it's 'error', fix the underlying token/permissions before retrying.
- Confirm the connection was created as a Railway integration (isRailwayConnection) rather than a plain MCP endpoint with a Railway URL.
Example fix
// before
const conn = { config: { url: "https://railway.example.com", railwayApiStatus: "unverified" } };
await runRailwayTool(conn, "railway_run-command", args); // 422
// after
await refreshConnection(conn.id); // re-verifies Railway API
const verified = await getConnection(conn.id);
if (verified.config.railwayApiStatus === "available") await runRailwayTool(verified, "railway_run-command", args); Defensive patterns
Strategy: validation
Validate before calling
if (conn.config?.railwayApiStatus !== "available") {
await refreshActions(conn.id); // re-verify Railway API before invoking railway_* tools
} Type guard
const isVerifiedRailway = (c) => Boolean(c && c.config?.railwayApiStatus === "available" && typeof c.config?.railwaySsh === "object");
Try / catch
try {
await gateway.invoke({ method: "tools/call", params: { name: "railway_run-command", arguments: args } });
} catch (e) {
if (e.reasonCode === "railway_api_not_verified") {
await reconnectAndVerifyRailway(e.details?.connectionId);
} else throw e;
} Prevention
- Run the Railway API verification (refresh actions) immediately after creating or updating a Railway connection.
- Alert on railwayApiStatus leaving "available" (token revocation, expiry).
- Ensure the connection is created through the Railway integration flow, not as a generic MCP endpoint.
- Periodically re-verify long-lived Railway tokens.
When it happens
Trigger: Invoking a railway_* tool when connection.config.railwayApiStatus !== "available" (e.g. the token was never verified, verification failed, or the connection is a generic MCP endpoint that merely matches isRailwayEndpoint), or the connection lacks isRailwayConnection typing.
Common situations: User added a Railway URL endpoint but never ran the 'refresh actions'/API-verification step; the Railway API token was revoked so re-verification flipped status away from 'available'; connection config was hand-edited and railwayApiStatus is missing.
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_ssh_setup_required
- railway_workspace_required
- A trusted viewer build is required for public chat reports
- ACPX provider package manifest must be an explicit…
- ACPX provider package root must be an explicit normalized…
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/412a8504da73e380.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/tool-gateway.ts:5817
},
};
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), ms);
timer.unref?.();
try {
const dispatchRemote = (target: string, init: RequestInit) =>
options.remoteHttpRequest
? options.remoteHttpRequest(target, init)
: guardedRemoteHttpFetch(target, init, {
...remoteHttpFetchOptions(),
// This call site owns a caller-set budget that can exceed the
// transport's default response deadline, so hand it down rather than
// letting the tighter default cut a legitimately slow tool short.
responseTimeoutMs: ms,
});
if (isRailwayEndpoint(connection.config.url) && normalizeRailwayToolName(entry.toolName).startsWith(RAILWAY_TOOL_PREFIX)) {
if (!isRailwayConnection(connection) || connection.config.railwayApiStatus !== "available") {
throw new ToolGatewayHttpError(422, "Railway API access is not verified. Refresh actions or reconnect this Railway connection.", "railway_api_not_verified");
}
const ssh = asRecord(connection.config.railwaySsh);
const sshRef = grant.credentialSecretRefs.find((ref) => ref.configPath === RAILWAY_SSH_SECRET_PATH);
execution.request.endpoint = RAILWAY_API_URL;
execution.request.protocol = entry.toolName === `${RAILWAY_TOOL_PREFIX}run-command` ? "Railway GraphQL + SSH" : "Railway GraphQL";
const client = createRailwayClient({
authorization: credentialHeaders.Authorization ?? "",
signal: controller.signal,
request: dispatchRemote,
runCommand: ssh?.grantId === grant.id && ssh?.enabled === true && sshRef
? async (input) => runRailwaySshCommand({
...input,
privateKey: await resolveGrantSecretValue(session, connection, grant, sshRef),
knownHosts: typeof ssh.knownHosts === "string" ? ssh.knownHosts : "",
})
: undefined,
});
const data = await client.call(entry.toolName, parameters);View on GitHub (pinned to 3f1d897a7c)