paperclipai/paperclip · error · ToolGatewayHttpError

railway_action_blocked

railway_action_blocked

Error message

This Railway action cannot bind its effects to an approved target. Use redeploy, restart, or rollback for an existing deployment.

What it means

This ToolGatewayHttpError (HTTP 403) is thrown by the tool gateway's governed-tool pre-check when the resolved connection's config URL points at Railway (isRailwayEndpoint) and the requested tool is on the Railway blocked list (isRailwayToolBlocked). Railway tools that cannot be bound to an approved target are refused because their effects could not be tied to a deployment the operator explicitly approved. The message tells the caller to use redeploy, restart, or rollback against an existing deployment instead.

Solutions

  1. Replace the blocked tool call with an allowed Railway action — redeploy, restart, or rollback of an existing deployment — so the effect binds to an approved target.
  2. If the workflow truly needs the blocked action, perform it directly in the Railway dashboard/CLI outside the governed gateway, then let the agent operate on the resulting deployment.
  3. Check isRailwayToolBlocked in tool-gateway.ts to confirm which tool names are blocked and choose a non-blocked equivalent.
  4. If the connection is being misdetected as Railway (isRailwayEndpoint matching a non-Railway URL), correct the connection's config.url.

Example fix

// before: blocked provisioning tool
callTool(connection, "create_deployment", { environmentId });
// after: allowed action on an existing deployment
callTool(connection, "redeploy", { deploymentId: existingDeployment.id });
Defensive patterns

Strategy: validation

Validate before calling

if (isRailwayEndpoint(connection.config.url) && isRailwayToolBlocked(entry.toolName)) {
  throw new Error(`Tool ${entry.toolName} is blocked for Railway connections; use redeploy, restart, or rollback.`);
}

Type guard

function isAllowedRailwayTool(connection: Connection, toolName: string): boolean {
  return !(isRailwayEndpoint(connection.config.url) && isRailwayToolBlocked(toolName));
}

Try / catch

try {
  await callGovernedTool(session, connectionId, toolName, args);
} catch (e) {
  if (e instanceof ToolGatewayHttpError && e.code === "railway_action_blocked") {
    return { status: "blocked", remediation: "Use redeploy, restart, or rollback on an existing deployment." };
  }
  throw e;
}

Prevention

When it happens

Trigger: A session resolves a connection whose config.url matches a Railway endpoint and the tool entry's name is in the blocked set (e.g. create/delete-deployment style Railway tools); the call reaches resolveGovernedEntry's final check at tool-gateway.ts:4679 before any protocol call is made.

Common situations: An agent plans a workflow that would create a brand-new Railway deployment; a user wires a generic Railway MCP server into Paperclip and the agent picks a provisioning tool; plugin or skill prompts suggest destructive Railway actions that the gateway intentionally blocks.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/95d1883d5f9e7252. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/tool-gateway.ts:4679

    if (!connection || connection.transport !== "mcp_remote") {
      throw new ToolGatewayHttpError(
        404,
        `Tool "${tool.name}" not found`,
        "tool_not_found",
      );
    }
    if (!connection.enabled || connection.status !== "active") {
      throw new ToolGatewayHttpError(
        403,
        "Connection is disabled.",
        "mcp_remote_connection_disabled",
        {
          connectionId: connection.id,
        },
      );
    }
    if (isRailwayEndpoint(connection.config.url) && isRailwayToolBlocked(entry.toolName)) {
      throw new ToolGatewayHttpError(403, "This Railway action cannot bind its effects to an approved target. Use redeploy, restart, or rollback for an existing deployment.", "railway_action_blocked");
    }
    return { entry, connection };
  }

  async function governedToolArguments(
    session: ToolGatewaySession,
    tool: ToolGatewayDescriptor,
    parameters: unknown,
  ): Promise<unknown> {
    if (tool.providerType !== "mcp_remote_http") return parameters;
    const { connection } = await resolveConnectedRemoteTool(session, tool);
    return projectedConnectionToolArguments(connection, parameters);
  }

  async function approvedManagedArgumentsRemainCurrent(
    session: ToolGatewaySession,
    tool: ToolGatewayDescriptor,
    reviewedParameters: unknown,

View on GitHub (pinned to 3f1d897a7c)