@@ -97,7 +97,7 @@ func (*App) githubToken(ctx context.Context) (string, error) {
9797 }
9898
9999 if ghPath == "" {
100- return "" , errors .New ("gh cli not found in trusted locations, please install from https://cli.github.com " )
100+ return "" , errors .New ("gh cli not found in trusted locations" )
101101 }
102102
103103 log .Printf ("Executing command: %s auth token" , ghPath )
@@ -141,42 +141,59 @@ func (app *App) fetchPRs(ctx context.Context) (incoming []PR, outgoing []PR, err
141141 log .Printf ("Searching for PRs with query: %s" , query )
142142 searchStart := time .Now ()
143143
144- // Create timeout context for GitHub API call
145- githubCtx , cancel := context .WithTimeout (ctx , 30 * time .Second )
146- defer cancel ()
147-
148- result , resp , err := app .client .Search .Issues (githubCtx , query , opts )
149- if err != nil {
150- // Enhanced error handling with specific cases
151- if resp != nil {
152- const (
153- httpStatusUnauthorized = 401
154- httpStatusForbidden = 403
155- httpStatusUnprocessable = 422
156- )
157- switch resp .StatusCode {
158- case httpStatusForbidden :
159- if resp .Header .Get ("X-Ratelimit-Remaining" ) == "0" {
160- resetTime := resp .Header .Get ("X-Ratelimit-Reset" )
161- log .Printf ("GitHub API rate limited, reset at: %s" , resetTime )
162- return nil , nil , fmt .Errorf ("github API rate limited, try again later: %w" , err )
144+ var result * github.IssuesSearchResult
145+ var resp * github.Response
146+ err = retry .Do (func () error {
147+ // Create timeout context for GitHub API call
148+ githubCtx , cancel := context .WithTimeout (ctx , 30 * time .Second )
149+ defer cancel ()
150+
151+ var retryErr error
152+ result , resp , retryErr = app .client .Search .Issues (githubCtx , query , opts )
153+ if retryErr != nil {
154+ // Enhanced error handling with specific cases
155+ if resp != nil {
156+ const (
157+ httpStatusUnauthorized = 401
158+ httpStatusForbidden = 403
159+ httpStatusUnprocessable = 422
160+ )
161+ switch resp .StatusCode {
162+ case httpStatusForbidden :
163+ if resp .Header .Get ("X-Ratelimit-Remaining" ) == "0" {
164+ resetTime := resp .Header .Get ("X-Ratelimit-Reset" )
165+ log .Printf ("GitHub API rate limited, reset at: %s (will retry)" , resetTime )
166+ return retryErr // Retry on rate limit
167+ }
168+ log .Print ("GitHub API access forbidden (check token permissions)" )
169+ return retry .Unrecoverable (fmt .Errorf ("github API access forbidden: %w" , retryErr ))
170+ case httpStatusUnauthorized :
171+ log .Print ("GitHub API authentication failed (check token)" )
172+ return retry .Unrecoverable (fmt .Errorf ("github API authentication failed: %w" , retryErr ))
173+ case httpStatusUnprocessable :
174+ log .Printf ("GitHub API query invalid: %s" , query )
175+ return retry .Unrecoverable (fmt .Errorf ("github API query invalid: %w" , retryErr ))
176+ default :
177+ log .Printf ("GitHub API error (status %d): %v (will retry)" , resp .StatusCode , retryErr )
163178 }
164- log .Print ("GitHub API access forbidden (check token permissions)" )
165- return nil , nil , fmt .Errorf ("github API access forbidden: %w" , err )
166- case httpStatusUnauthorized :
167- log .Print ("GitHub API authentication failed (check token)" )
168- return nil , nil , fmt .Errorf ("github API authentication failed: %w" , err )
169- case httpStatusUnprocessable :
170- log .Printf ("GitHub API query invalid: %s" , query )
171- return nil , nil , fmt .Errorf ("github API query invalid: %w" , err )
172- default :
173- log .Printf ("GitHub API error (status %d): %v" , resp .StatusCode , err )
179+ } else {
180+ // Likely network error - retry these
181+ log .Printf ("GitHub API network error: %v (will retry)" , retryErr )
174182 }
175- } else {
176- // Likely network error
177- log .Printf ("GitHub API network error: %v" , err )
183+ return retryErr
178184 }
179- return nil , nil , fmt .Errorf ("search PRs: %w" , err )
185+ return nil
186+ },
187+ retry .Attempts (maxRetries ),
188+ retry .DelayType (retry .BackOffDelay ),
189+ retry .MaxDelay (maxRetryDelay ),
190+ retry .OnRetry (func (n uint , err error ) {
191+ log .Printf ("GitHub Search.Issues retry %d/%d: %v" , n + 1 , maxRetries , err )
192+ }),
193+ retry .Context (ctx ),
194+ )
195+ if err != nil {
196+ return nil , nil , fmt .Errorf ("search PRs after %d retries: %w" , maxRetries , err )
180197 }
181198
182199 log .Printf ("GitHub search completed in %v, found %d PRs" , time .Since (searchStart ), len (result .Issues ))
@@ -288,30 +305,11 @@ func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue,
288305 go func (issue * github.Issue ) {
289306 defer wg .Done ()
290307
291- // Retry logic for Turn API with exponential backoff and jitter
292- var turnData * turn.CheckResponse
293- var err error
308+ url := issue .GetHTMLURL ()
309+ updatedAt := issue .GetUpdatedAt ().Time
294310
295- turnData , err = retry .DoWithData (
296- func () (* turn.CheckResponse , error ) {
297- data , apiErr := app .turnData (ctx , issue .GetHTMLURL (), issue .GetUpdatedAt ().Time )
298- if apiErr != nil {
299- log .Printf ("Turn API attempt failed for %s: %v" , issue .GetHTMLURL (), apiErr )
300- }
301- return data , apiErr
302- },
303- retry .Context (ctx ),
304- retry .Attempts (5 ), // 5 attempts max
305- retry .Delay (500 * time .Millisecond ), // Start with 500ms
306- retry .MaxDelay (30 * time .Second ), // Cap at 30 seconds
307- retry .DelayType (retry .FullJitterBackoffDelay ), // Exponential backoff with jitter
308- retry .OnRetry (func (attempt uint , err error ) {
309- log .Printf ("Turn API retry attempt %d for %s: %v" , attempt , issue .GetHTMLURL (), err )
310- }),
311- )
312- if err != nil {
313- log .Printf ("Turn API failed after all retries for %s: %v" , issue .GetHTMLURL (), err )
314- }
311+ // Call turnData - it now has proper exponential backoff with jitter
312+ turnData , err := app .turnData (ctx , url , updatedAt )
315313
316314 results <- prResult {
317315 url : issue .GetHTMLURL (),
@@ -340,22 +338,9 @@ func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue,
340338 const minUpdateInterval = 500 * time .Millisecond
341339
342340 for result := range results {
343- // Debug logging for PR #1203 - check all responses
344- if strings .Contains (result .url , "1203" ) {
345- log .Printf ("[TURN] DEBUG PR #1203: result.err=%v, turnData=%v" , result .err , result .turnData != nil )
346- if result .turnData != nil {
347- log .Printf ("[TURN] DEBUG PR #1203: PRState.UnblockAction=%v" , result .turnData .PRState .UnblockAction != nil )
348- }
349- }
350-
351341 if result .err == nil && result .turnData != nil && result .turnData .PRState .UnblockAction != nil {
352342 turnSuccesses ++
353343
354- // Debug logging for PR #1203
355- if strings .Contains (result .url , "1203" ) {
356- log .Printf ("[TURN] DEBUG PR #1203: UnblockAction keys: %+v" , result .turnData .PRState .UnblockAction )
357- }
358-
359344 // Check if user needs to review and get action reason
360345 needsReview := false
361346 actionReason := ""
0 commit comments