Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 19, 2026

Summary

Adds exponential backoff with random jitter to sub_issue_write operations that fail with priority conflicts when called in parallel.

Why

AI agents frequently call sub_issue_write in parallel when adding multiple sub-issues to an epic. GitHub's API assigns priority sequentially, causing parallel calls to fail with 422 Priority has already been taken errors.

What changed

  • Modified AddSubIssue to retry up to 3 times on priority conflict errors (422 with "Priority has already been taken")
  • Added 50-200ms random jitter between retries using math/rand/v2.IntN() (thread-safe, auto-seeded)
  • Stores response body to avoid re-reading closed streams
  • Fixed resource leak by ensuring response body is properly closed in all code paths including success path
  • Added Test_AddSubIssue_RetryLogic with 4 test cases covering success/failure/non-retryable scenarios
  • Used atomic.Int32 for thread-safe test counters

MCP impact

  • Tool schema or behavior changed
    • sub_issue_write with add method now auto-retries priority conflicts. No schema changes.

Prompts tested (tool changes only)

Security / limits

  • No security or limits impact
    • Jitter uses non-cryptographic random (acceptable for backoff)
    • Max 3 retries limits additional API calls

Tool renaming

  • I am not renaming tools as part of this PR

Lint & tests

  • Linted locally with ./script/lint
  • Tested locally with ./script/test

Docs

  • Not needed
    • Behavior change is transparent to users; no documentation update required
Original prompt

This section details on the original issue you should resolve

<issue_title>Add retry logic with jitter for sub_issue_write to handle parallel calls</issue_title>
<issue_description>## Problem

When sub_issue_write tool is called in parallel (common with AI agents), multiple calls can fail with:

422 An error occurred while adding the sub-issue to the parent issue. Priority has already been taken

This happens because GitHub's sub-issues API assigns priority sequentially, and parallel calls conflict when they try to claim the same priority slot.

Proposed Solution

Add retry logic with random jitter to sub_issue_write operations:

  1. Retry up to 3 times on 422 errors related to priority conflicts
  2. Add random jitter (e.g., 50-200ms) before each retry to desynchronize parallel calls
  3. Only retry on specific "Priority has already been taken" errors, not all 422s

Example Implementation

func (s *SubIssueService) Add(ctx context.Context, owner, repo string, issueNum int, subIssueID int64) (*github.Issue, error) {
    const maxRetries = 3
    
    for attempt := 0; attempt < maxRetries; attempt++ {
        if attempt > 0 {
            // Random jitter: 50-200ms to desynchronize parallel retries
            jitter := time.Duration(50+rand.Intn(150)) * time.Millisecond
            time.Sleep(jitter)
        }
        
        issue, resp, err := s.client.Issues.AddSubIssue(ctx, owner, repo, issueNum, subIssueID)
        if err == nil {
            return issue, nil
        }
        
        // Only retry on priority conflict errors
        if resp != nil && resp.StatusCode == 422 && strings.Contains(err.Error(), "Priority has already been taken") {
            continue
        }
        
        return nil, err
    }
    
    return nil, fmt.Errorf("failed after %d retries: priority conflict", maxRetries)
}

Why This Matters

Parallel tool calling is very popular with AI agents. When an agent needs to add multiple sub-issues to an epic, it naturally calls them in parallel for efficiency. Without retry logic, a significant portion of these calls fail, degrading the user experience.

Acceptance Criteria

  • sub_issue_write with add method retries up to 3 times on priority conflicts
  • Random jitter (50-200ms) is added between retries
  • Other 422 errors are not retried
  • Unit tests cover retry behavior
  • Existing functionality is preserved</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 7 commits January 19, 2026 21:23
Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Copilot AI changed the title [WIP] Add retry logic with jitter for sub-issue write Add retry logic with jitter for sub_issue_write to handle parallel calls Jan 19, 2026
Copilot AI requested a review from SamMorrowDrums January 19, 2026 21:37
@SamMorrowDrums SamMorrowDrums marked this pull request as ready for review January 19, 2026 22:16
@SamMorrowDrums SamMorrowDrums requested a review from a team as a code owner January 19, 2026 22:16
Copilot AI review requested due to automatic review settings January 19, 2026 22:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds exponential backoff with random jitter to the sub_issue_write tool to handle parallel API calls that fail due to GitHub's sequential priority assignment. AI agents frequently call this tool in parallel when adding multiple sub-issues, causing 422 "Priority has already been taken" errors.

Changes:

  • Implemented retry logic with up to 3 attempts for priority conflict errors (422 status with specific message)
  • Added 50-200ms random jitter between retries using math/rand/v2.IntN() for desynchronization
  • Added comprehensive test coverage with 4 scenarios: successful retry, exhausted retries, non-retryable errors, and immediate success

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
pkg/github/issues.go Refactored AddSubIssue function to implement retry logic with jitter, improved error handling, and response body management
pkg/github/issues_test.go Added Test_AddSubIssue_RetryLogic with 4 test cases covering retry scenarios using atomic counters for thread-safe mock handlers

Comment on lines 833 to 839
if err == nil && resp.StatusCode == http.StatusCreated {
r, err := json.Marshal(subIssue)
if err != nil {
return nil, fmt.Errorf("failed to marshal response: %w", err)
}
return utils.NewToolResultText(string(r)), nil
}
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential resource leak: The response body is not closed in the success path. The original code had a deferred close for the response body, but in the retry logic, if the request succeeds, the response body should still be closed to prevent resource leaks. Consider adding _ = resp.Body.Close() before returning on success.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot can you check this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8e0a81f. Added _ = resp.Body.Close() before returning in the success path to prevent the resource leak.

Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add retry logic with jitter for sub_issue_write to handle parallel calls

2 participants