gastownhall/beads · error
failed after %d retries: %w
Error message
failed after %d retries: %w
What it means
When every attempt in callWithRetry fails with a retryable error, the client gives up after h.maxRetries retries (maxRetries+1 total attempts) and wraps the final error with this message. It signals sustained unavailability or rate limiting of the AI endpoint, not a client bug.
Source
Thrown at internal/compact/haiku.go:204
lastErr = err
if ctx.Err() != nil {
return "", ctx.Err()
}
if !isRetryable(err) {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return "", fmt.Errorf("non-retryable error: %w", err)
}
}
if lastErr != nil {
span.RecordError(lastErr)
span.SetStatus(codes.Error, lastErr.Error())
}
return "", fmt.Errorf("failed after %d retries: %w", h.maxRetries+1, lastErr)
}
func isRetryable(err error) bool {
if err == nil {
return false
}
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return false
}
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
return true
}
var apiErr *anthropic.Error
if errors.As(err, &apiErr) {View on GitHub (pinned to 71377f2769)
Solutions
- Wait for the rate-limit window to reset and re-run compaction (respect Retry-After from the wrapped 429)
- Throttle batch compaction — process fewer issues concurrently or add delay between calls
- Increase maxRetries/backoff if transient outages are common in your environment
- Use a dedicated or higher-tier API key to raise rate limits; check status.anthropic.com for outages
Example fix
// before
client, err := compact.New(key) // default retries
// after
// configure larger retry budget / backoff via client options if the API exposes it,
// or serialize calls:
for _, iss := range issues {
sum, err := client.SummarizeTier1(ctx, iss)
if err != nil {
time.Sleep(30 * time.Second) // back off on 429 before continuing
}
} Defensive patterns
Strategy: retry
Try / catch
summary, err := client.SummarizeTier1(ctx, issue)
if err != nil && strings.Contains(err.Error(), "failed after") {
select {
case <-time.After(backoff):
summary, err = client.SummarizeTier1(ctx, issue)
case <-ctx.Done():
return ctx.Err()
}
} Prevention
- Serialize or rate-limit batch compaction to stay under quotas
- Use a dedicated API key with sufficient rate limits
- Add generous timeouts and exponential backoff around bulk runs
- Monitor provider status pages during known incident windows
When it happens
Trigger: All maxRetries+1 attempts of the API call return retryable errors (429 rate limit, 5xx server errors, transient network timeouts) so lastErr is wrapped after the loop exits.
Common situations: Hitting Anthropic rate limits during batch compaction of many issues; provider outage (5xx); corporate proxy or flaky network dropping connections; per-minute token quota exhausted on a shared key.
Related errors
- max retries (%d) exceeded: %w
- transient error %d (attempt %d/%d)
- non-retryable error: %w
- request failed (attempt %d/%d): %w
- failed to read response (attempt %d/%d): %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/bee555c8ebba3805.
Report an issue: GitHub.