apple/pkl · error · HttpClientException

httpTooManyRedirects

httpTooManyRedirects

Error message

httpTooManyRedirects: ${max}

What it means

Pkl's HTTP client followed the maximum number of HTTP redirects (MAX_HTTP_REDIRECTS) without reaching a final response. doSend tracks redirectCount across responses and throws httpTooManyRedirects when the limit is reached, as a guard against redirect loops.

Source

Thrown at pkl-core/src/main/java/org/pkl/core/http/RequestRewritingClient.java:126

  private <T> HttpResponse<T> doSend(
      HttpRequest request,
      BodyHandler<T> responseBodyHandler,
      HttpRequestChecker httpRequestChecker)
      throws SecurityManagerException, IOException {
    var redirectCount = 0;
    var currentRequestUri = rewriteUri(request.uri());
    var currentRequest = rewriteRequest(request, currentRequestUri);
    while (true) {
      httpRequestChecker.check(currentRequestUri);
      var response = delegate.send(currentRequest, responseBodyHandler, httpRequestChecker);
      if (!HttpUtils.isRedirectStatusCode(response.statusCode())) {
        return response;
      }
      if (response.body() instanceof Closeable closeable) {
        closeable.close();
      }
      if (redirectCount >= MAX_HTTP_REDIRECTS) {
        throw new HttpClientException(
            ErrorMessages.create("httpTooManyRedirects", MAX_HTTP_REDIRECTS));
      }
      var location = response.headers().firstValue("Location");
      if (location.isEmpty()) {
        throw new HttpClientException(
            ErrorMessages.create("httpRedirectNoLocation", currentRequestUri));
      }
      URI redirectUri;
      try {
        redirectUri = currentRequestUri.resolve(location.get());
      } catch (IllegalArgumentException e) {
        throw new HttpClientException(
            ErrorMessages.create("httpRedirectInvalidUri", currentRequestUri, location.get()));
      }
      if (currentRequestUri.getScheme().equalsIgnoreCase("https")
          && redirectUri.getScheme().equalsIgnoreCase("http")) {
        throw new HttpClientException(
            ErrorMessages.create("httpRedirectCannotDowngrade", currentRequestUri, redirectUri));

View on GitHub (pinned to f3efcbfc9b)

Solutions

  1. Open the URL in curl (`curl -IL <url>`) and inspect the Location headers to find the redirect loop.
  2. Fix the loop on the server side (rewrite rules, trailing-slash handling, http-to-https configuration).
  3. If the target moved, update the Pkl import/dependency URL to the final destination.
  4. Check for a proxy rewriting URLs into a cycle (see no-proxy/proxy config).
  5. As a workaround, serve the content at a stable, non-redirecting URL.

Example fix

// before (nginx)
rewrite ^/pkg/$ /pkg permanent;   # /pkg -> /pkg/ -> /pkg loop
// after (nginx)
location /pkg { try_files $uri $uri/ =404; }
Defensive patterns

Strategy: retry

Validate before calling

HttpURLConnection c = (HttpURLConnection) URI.create(targetUrl).toURL().openConnection();
c.setInstanceFollowRedirects(false);
int code = c.getResponseCode();
Set<String> seen = new HashSet<>();
while (300 <= code && code < 400) {
  String loc = c.getHeaderField("Location");
  if (loc == null || !seen.add(loc)) throw new IllegalStateException("Redirect loop at " + loc);
  c = (HttpURLConnection) URI.create(loc).toURL().openConnection();
  code = c.getResponseCode();
}

Try / catch

try {
  // fetch module via Pkl
} catch (HttpClientException e) {
  if (e.getMessage().startsWith("httpTooManyRedirects")) {
    // diagnose loop with curl -IL, point the import at the final URL
  }
}

Prevention

When it happens

Trigger: A Pkl `import`/module fetch over HTTP encounters a server (or chain of servers) whose responses keep redirecting — typically a redirect loop (A -> B -> A) or a chain longer than MAX_HTTP_REDIRECTS. doSend throws after closing each interim response body.

Common situations: A misconfigured web server redirecting between trailing-slash and non-trailing-slash URLs forever, http<->https ping-pong caused by conflicting TLS settings, a load balancer loop, or an extremely long redirect chain.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08). Data as JSON: /api/errors/b0720d670bd97142. Report an issue: GitHub.