hashicorp/terraform · error

the request failed after

Error message

the request failed after %d attempts, please try again later%s

What it means

Final error returned by the retryablehttp maxRetryErrorHandler when the request exhausted all retry attempts and numTries > 1. Reports the total attempt count and the trailing status-or-error detail. This is the terminal failure after RetryMax retries have all failed.

Solutions

  1. Increase TF_REGISTRY_DISCOVERY_RETRY (e.g. to 5 or 10) to extend the retry window.
  2. Increase TF_REGISTRY_CLIENT_TIMEOUT for slow links.
  3. Verify network connectivity and DNS to the registry host.
  4. If the trailing detail is a 5xx, check the registry status page; if it is a transport error, inspect TLS/proxy.
  5. Switch to a mirror or filesystem provider source if the registry outage is prolonged.

Example fix

// before
Error: the request failed after 2 attempts, please try again later: 502 Bad Gateway returned from registry.terraform.io
// after
$ export TF_REGISTRY_DISCOVERY_RETRY=10
$ export TF_REGISTRY_CLIENT_TIMEOUT=30
$ terraform init
Defensive patterns

Strategy: retry

Type guard

// Detect an exhausted-retries error.
func isExhaustedRetries(err error) bool {
    return err != nil && strings.Contains(err.Error(), "the request failed after")
}

Try / catch

// Increase retry budget and retry the whole operation once.
if strings.Contains(err.Error(), "failed after") {
    os.Setenv("TF_REGISTRY_DISCOVERY_RETRY", "10")
    return runInit()
}

Prevention

When it happens

Trigger: Every retryable attempt (default 1 + retries from TF_REGISTRY_DISCOVERY_RETRY) returned a retryable error (5xx, network error) and the budget is spent; numTries equals RetryMax+1 and the handler is invoked.

Common situations: Sustained registry outage; saturated network link; DNS misconfiguration persisting across the retry window; rate limiter (429 treated as retryable) staying active throughout the window.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/fad82f5bc86267bb. Report an issue: GitHub.

Appendix: source

Thrown at internal/getproviders/registry_client.go:490

	// Close the body per library instructions
	if resp != nil {
		resp.Body.Close()
	}

	// Additional error detail: if we have a response, use the status code;
	// if we have an error, use that; otherwise nothing. We will never have
	// both response and error.
	var errMsg string
	if resp != nil {
		errMsg = fmt.Sprintf(": %s returned from %s", resp.Status, HostFromRequest(resp.Request))
	} else if err != nil {
		errMsg = fmt.Sprintf(": %s", err)
	}

	// This function is always called with numTries=RetryMax+1. If we made any
	// retry attempts, include that in the error message.
	if numTries > 1 {
		return resp, fmt.Errorf("the request failed after %d attempts, please try again later%s",
			numTries, errMsg)
	}
	return resp, fmt.Errorf("the request failed, please try again later%s", errMsg)
}

// HostFromRequest extracts host the same way net/http Request.Write would,
// accounting for empty Request.Host
func HostFromRequest(req *http.Request) string {
	if req.Host != "" {
		return req.Host
	}
	if req.URL != nil {
		return req.URL.Host
	}

	// this should never happen and if it does
	// it will be handled as part of Request.Write()
	// https://cs.opensource.google/go/go/+/refs/tags/go1.18.4:src/net/http/request.go;l=574

View on GitHub (pinned to d32a084675)