gastownhall/beads · error
max retries (%d) exceeded: %w
Error message
max retries (%d) exceeded: %w
What it means
doRequest exhausted all retry attempts against the Azure DevOps REST API without getting a non-transient response. The final transient status error (lastErr, see 1407) is wrapped via %w so callers can errors.Unwrap/errors.As into it. Every API method (FetchWorkItems, WIQL queries, work-item create/update/link operations) funnels through doRequest, so any of them can produce this.
Source
Thrown at internal/ado/client.go:286
// Only add jitter to our own exponential backoff, not server-mandated delays
if !useServerDelay {
if half := int64(delay / 2); half > 0 {
delay += time.Duration(rand.Int64N(half)) //nolint:gosec // G404: jitter for retry backoff does not need crypto rand
}
}
lastErr = fmt.Errorf("transient error %d (attempt %d/%d)", resp.StatusCode, attempt+1, maxAttempts+1)
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(delay):
continue
}
}
return nil, &APIError{StatusCode: resp.StatusCode, Body: string(respBody)}
}
return nil, fmt.Errorf("max retries (%d) exceeded: %w", maxAttempts+1, lastErr)
}
// addAPIVersion appends the api-version query parameter to a URL string.
func addAPIVersion(urlStr string) string {
if strings.Contains(urlStr, "?") {
return urlStr + "&api-version=" + APIVersion
}
return urlStr + "?api-version=" + APIVersion
}
// listResponse is a generic envelope for ADO list API responses.
type listResponse struct {
Count int `json:"count"`
Value json.RawMessage `json:"value"`
}
// escapeWIQL escapes a string for safe inclusion in a WIQL query literal.
func escapeWIQL(s string) string {View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped cause (`errors.Unwrap` or %v of the error) to see the last status code and target accordingly (429 vs 503).
- Increase backoff between your own calls or reduce concurrency/batch size to avoid rate limits.
- Increase the client's retry budget or your operation timeout if the deadline truncates retries.
- Check Azure DevOps status page for a service incident; wait and retry.
- Verify network/proxy configuration if 5xx comes from an intermediate hop.
Example fix
// before
items, err := client.FetchWorkItems(ctx, ids) // fails under sustained 429
if err != nil { return err }
// after
items, err := client.FetchWorkItems(ctx, ids)
if err != nil {
var apiErr *ado.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == 429 {
time.Sleep(retryAfterOr(30*time.Second)); items, err = client.FetchWorkItems(ctx, ids)
}
} Defensive patterns
Strategy: retry
Validate before calling
// pre-flight: verify PAT validity and org reachability before long batch jobs
resp, _ := http.Get(orgURL + "/_apis/projects?api-version=" + ado.APIVersion)
if resp.StatusCode != 200 { /* abort early: auth or org misconfigured */ } Try / catch
items, err := client.FetchWorkItems(ctx, ids)
if err != nil {
var apiErr *ado.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == 429 {
// schedule retry with long backoff
} else {
return fmt.Errorf("non-retryable: %w", err)
}
} Prevention
- Add an outer retry-with-long-backoff around library calls for batch jobs.
- Cap concurrency against ADO to stay under rate limits.
- Set context deadlines longer than the client's total backoff window.
- Subscribe to Azure DevOps service health before running large migrations.
- Log the wrapped cause (%v) — it reveals the decisive status code.
When it happens
Trigger: Any ADO API call where every attempt (maxAttempts+1) returned a retryable status (429/5xx) or network-level transient failure. Raised after the retry loop completes with lastErr non-nil and no successful response or non-retryable APIError.
Common situations: Sustained rate limiting from bulk imports/syncs; prolonged Azure DevOps outage; network partitions between CI and dev.azure.com; misconfigured proxy returning 5xx; context timeouts shorter than total backoff window.
Related errors
- transient error %d (attempt %d/%d)
- failed to list projects: %w
- failed to fetch work items: %w
- failed to add work item link: %w
- failed to remove work item link: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/cb32b63316481854.
Report an issue: GitHub.