Compare commits

...

1 Commits

Author SHA1 Message Date
lukaszraczylo 09b0c533b4 Add retry logic for GitHub graphql requests 2025-12-12 01:56:52 +00:00
2 changed files with 58 additions and 3 deletions
+5 -1
View File
@@ -301,12 +301,16 @@ func isRetryableError(err error) bool {
"timeout", "timeout",
"temporary failure", "temporary failure",
"server error", "server error",
"stream error",
"CANCEL",
"EOF",
"broken pipe",
"502", "502",
"503", "503",
"504", "504",
} }
for _, msg := range retryableMessages { for _, msg := range retryableMessages {
if strings.Contains(strings.ToLower(errStr), msg) { if strings.Contains(strings.ToLower(errStr), strings.ToLower(msg)) {
return true return true
} }
} }
+53 -2
View File
@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"strings"
"time" "time"
"github.com/charmbracelet/bubbles/progress" "github.com/charmbracelet/bubbles/progress"
@@ -133,8 +134,28 @@ func fetchGQLPaginated[Q any, T any, R any](
} }
for { for {
if err := client.Query(ctx, config.Query, variables); err != nil { // Retry logic for transient errors
return nil, fmt.Errorf("graphql query failed: %w", err) var queryErr error
for retries := 0; retries < 3; retries++ {
queryErr = client.Query(ctx, config.Query, variables)
if queryErr == nil {
break
}
// Check if error is retryable
if !isGQLRetryableError(queryErr) {
break
}
// Wait before retry with exponential backoff
backoff := time.Duration(1<<retries) * time.Second
fmt.Fprintf(os.Stderr, "\r GraphQL retry %d/3 (waiting %s): %v\n", retries+1, backoff, queryErr)
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(backoff):
}
}
if queryErr != nil {
return nil, fmt.Errorf("graphql query failed: %w", queryErr)
} }
page := config.GetPageResult(config.Query) page := config.GetPageResult(config.Query)
@@ -520,3 +541,33 @@ func convertCommentNode(node gqlCommentNode, repoName string, issueNumber int) m
CreatedAt: node.CreatedAt, CreatedAt: node.CreatedAt,
} }
} }
// isGQLRetryableError checks if a GraphQL error is transient and should be retried
func isGQLRetryableError(err error) bool {
if err == nil {
return false
}
errStr := strings.ToLower(err.Error())
retryablePatterns := []string{
"stream error",
"cancel",
"eof",
"connection reset",
"connection refused",
"timeout",
"temporary failure",
"broken pipe",
"502",
"503",
"504",
}
for _, pattern := range retryablePatterns {
if strings.Contains(errStr, pattern) {
return true
}
}
return false
}