diff --git a/src/Runner.Common/RedirectMessageHandler.cs b/src/Runner.Common/RedirectMessageHandler.cs
new file mode 100644
index 000000000..81d216c01
--- /dev/null
+++ b/src/Runner.Common/RedirectMessageHandler.cs
@@ -0,0 +1,73 @@
+using System;
+using System.ComponentModel;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using GitHub.Runner.Sdk;
+using GitHub.Services.Common;
+
+namespace GitHub.Runner.Common
+{
+ ///
+ /// Handles redirects for Http requests
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class RedirectMessageHandler : DelegatingHandler
+ {
+ public RedirectMessageHandler(ITraceWriter trace)
+ {
+ Trace = trace;
+ }
+
+ protected override async Task SendAsync(
+ HttpRequestMessage request,
+ CancellationToken cancellationToken)
+ {
+ HttpResponseMessage response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
+
+ if (response != null &&
+ IsRedirect(response.StatusCode) &&
+ response.Headers.Location != null)
+ {
+ Trace.Info($"Redirecting to '{response.Headers.Location}'.");
+
+ request = await CloneAsync(request, response.Headers.Location).ConfigureAwait(false);
+
+ response.Dispose();
+
+ response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
+ }
+
+ return response;
+ }
+
+ private static bool IsRedirect(HttpStatusCode statusCode)
+ {
+ return (int)statusCode >= 300 && (int)statusCode < 400;
+ }
+
+ private static async Task CloneAsync(HttpRequestMessage request, Uri requestUri)
+ {
+ var clone = new HttpRequestMessage(request.Method, requestUri)
+ {
+ Version = request.Version
+ };
+
+ request.Headers.ForEach(header => clone.Headers.TryAddWithoutValidation(header.Key, header.Value));
+
+ request.Options.ForEach(option => clone.Options.Set(new HttpRequestOptionsKey