thanos-io/thanos · error

maximum number of recursive iterations reached

Error message

maximum number of recursive iterations reached (%d)

What it means

Guard against infinite recursion: SRV lookups that return CNAME records are followed recursively, capped at maxIterations (8 by default). Exceeding it means the DNS chain is too deep or loops on itself, so lookupSRV aborts rather than spinning forever.

Solutions

  1. Inspect the DNS records for the name: dig SRV/CNAME and follow the chain to find the loop
  2. Fix the DNS zone to remove the CNAME loop or shorten the chain
  3. If legitimately deep chains are needed, raise the iteration cap via the exported LookupSRV maxIterations parameter
Defensive patterns

Strategy: validation

Validate before calling

// detect CNAME loops before discovery
seen := map[string]bool{}
for t, ok := followCNAME(name); ok; t, ok = followCNAME(t.Target) {
    if seen[t] { log.Fatalf("CNAME loop at %s", t) }; seen[t] = true
}

Try / catch

if err != nil && strings.Contains(err.Error(), "maximum number of recursive iterations") {
    return fmt.Errorf("broken DNS alias chain for %s: %w", name, err) // fix DNS, do not retry
}

Prevention

When it happens

Trigger: LookupSRV (or internal lookupSRV) follows a chain of more than 8 CNAME/SRV targets: currIteration > maxIterations on a nested call.

Common situations: CNAME loops in a DNS zone (target points back at itself); pathologically long CNAME chains; misconfigured service aliases creating a cycle.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/dec720d924591333. Report an issue: GitHub.

Appendix: source

Thrown at pkg/discovery/dns/miekgdns/resolver.go:29

	"github.com/pkg/errors"
)

// DefaultResolvConfPath is a common, default resolv.conf file present on linux server.
const DefaultResolvConfPath = "/etc/resolv.conf"

// Resolver is a drop-in Resolver for *part* of std lib Golang net.DefaultResolver methods.
type Resolver struct {
	ResolvConf string
}

func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*net.SRV, err error) {
	return r.lookupSRV(service, proto, name, 1, 8)
}

func (r *Resolver) lookupSRV(service, proto, name string, currIteration, maxIterations int) (cname string, addrs []*net.SRV, err error) {
	// We want to protect from infinite loops when resolving DNS records recursively.
	if currIteration > maxIterations {
		return "", nil, errors.Errorf("maximum number of recursive iterations reached (%d)", maxIterations)
	}
	var target string
	if service == "" && proto == "" {
		target = name
	} else {
		target = "_" + service + "._" + proto + "." + name
	}

	response, err := r.lookupWithSearchPath(target, dns.Type(dns.TypeSRV))
	if err != nil {
		return "", nil, err
	}

	for _, record := range response.Answer {
		switch addr := record.(type) {
		case *dns.SRV:
			addrs = append(addrs, &net.SRV{
				Weight:   addr.Weight,

View on GitHub (pinned to 35b8b99117)