Copilot Issue Assignment #1368
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Copilot Issue Assignment | |
| on: | |
| issues: | |
| types: [opened, labeled] | |
| schedule: | |
| # Run every 10 minutes to process queued issues | |
| - cron: '*/10 * * * *' | |
| workflow_dispatch: | |
| inputs: | |
| issue_number: | |
| description: 'Issue number to assign to Copilot' | |
| required: true | |
| type: number | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| actions: read | |
| jobs: | |
| assign-to-copilot: | |
| runs-on: [self-hosted, linux, x64] | |
| if: > | |
| github.event_name == 'workflow_dispatch' || | |
| github.event_name == 'schedule' || | |
| (github.event.action == 'opened' && startsWith(github.event.issue.title, 'Fix:')) || | |
| (github.event.action == 'labeled' && github.event.label.name == 'copilot-fix') | |
| steps: | |
| - name: Clean workspace | |
| run: | | |
| # Fix permissions and clean workspace to avoid checkout failures | |
| if [ -d "$GITHUB_WORKSPACE" ]; then | |
| sudo chown -R $(whoami):$(whoami) "$GITHUB_WORKSPACE" 2>/dev/null || true | |
| sudo chmod -R u+rwX "$GITHUB_WORKSPACE" 2>/dev/null || true | |
| rm -rf "$GITHUB_WORKSPACE"/* "$GITHUB_WORKSPACE"/.* 2>/dev/null || true | |
| fi | |
| continue-on-error: true | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| clean: true | |
| - name: Find queued issues | |
| id: find_queued | |
| if: github.event_name == 'schedule' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Find issues labeled 'copilot-queued' OR with 'Fix:' prefix | |
| # Priority: 1) copilot-queued, 2) Fix: prefix issues | |
| QUEUED_ISSUES=$(gh issue list --repo "${{ github.repository }}" --label "copilot-queued" --state open --json number --jq '.[].number' | head -1) | |
| if [ -z "$QUEUED_ISSUES" ]; then | |
| # No queued issues, search for 'Fix:' prefix issues | |
| QUEUED_ISSUES=$(gh issue list --repo "${{ github.repository }}" --search "Fix: in:title" --state open --json number --jq '.[].number' | head -1) | |
| fi | |
| if [ -n "$QUEUED_ISSUES" ]; then | |
| echo "number=$QUEUED_ISSUES" >> $GITHUB_OUTPUT | |
| echo "Found queued issue #${QUEUED_ISSUES}" | tee -a $GITHUB_STEP_SUMMARY | |
| else | |
| echo "No queued issues found" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "number=" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Determine issue number | |
| id: issue | |
| run: | | |
| if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then | |
| echo "number=${{ inputs.issue_number }}" >> $GITHUB_OUTPUT | |
| elif [ "${{ github.event_name }}" == "schedule" ]; then | |
| echo "number=${{ steps.find_queued.outputs.number }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Check Copilot agent concurrency | |
| id: check_concurrency | |
| if: steps.issue.outputs.number != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Retry logic with exponential backoff | |
| MAX_RETRIES=3 | |
| RETRY_COUNT=0 | |
| BACKOFF=2 | |
| while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do | |
| if ACTIVE_COUNT=$(gh pr list --author "app/copilot-swe-agent" --state open --repo "${{ github.repository }}" --json number 2>/dev/null | jq 'length'); then | |
| echo "Currently active Copilot agents: ${ACTIVE_COUNT}" | tee -a $GITHUB_STEP_SUMMARY | |
| MAX_CONCURRENT=3 | |
| if [ "${ACTIVE_COUNT}" -ge "${MAX_CONCURRENT}" ]; then | |
| echo "⚠️ Concurrency limit reached (${ACTIVE_COUNT}/${MAX_CONCURRENT})" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "should_wait=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "✅ Concurrency available (${ACTIVE_COUNT}/${MAX_CONCURRENT})" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "should_wait=false" >> $GITHUB_OUTPUT | |
| fi | |
| break | |
| else | |
| RETRY_COUNT=$((RETRY_COUNT + 1)) | |
| if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then | |
| WAIT_TIME=$((BACKOFF ** RETRY_COUNT)) | |
| echo "⚠️ API call failed, retrying in ${WAIT_TIME}s (attempt $((RETRY_COUNT + 1))/${MAX_RETRIES})..." | tee -a $GITHUB_STEP_SUMMARY | |
| sleep $WAIT_TIME | |
| else | |
| echo "❌ Failed to check concurrency after ${MAX_RETRIES} attempts" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "should_wait=true" >> $GITHUB_OUTPUT # Fail safe: queue the issue | |
| fi | |
| fi | |
| done | |
| - name: Queue issue if limit reached | |
| if: steps.check_concurrency.outputs.should_wait == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| ISSUE_NUMBER="${{ steps.issue.outputs.number }}" | |
| gh issue edit "${ISSUE_NUMBER}" --repo "${{ github.repository }}" --add-label "copilot-queued" | |
| gh issue comment "${ISSUE_NUMBER}" --repo "${{ github.repository }}" --body "🤖 **Copilot Agent Queue Status** | |
| This issue has been queued for Copilot assignment due to concurrency limits. | |
| - **Active agents**: $(gh pr list --author 'app/copilot-swe-agent' --state open --repo '${{ github.repository }}' --json number | jq 'length') | |
| - **Max concurrent**: 3 | |
| You can manually trigger assignment later using: | |
| \`\`\`bash | |
| gh workflow run copilot-issue-assignment.yml --field issue_number=${ISSUE_NUMBER} | |
| \`\`\`" | |
| echo "Issue #${ISSUE_NUMBER} queued" | tee -a $GITHUB_STEP_SUMMARY | |
| - name: Invoke Copilot coding agent | |
| if: steps.check_concurrency.outputs.should_wait == 'false' | |
| env: | |
| # Using PAT with Copilot permissions (see .github/COPILOT_PAT_SETUP.md) | |
| GH_TOKEN: ${{ secrets.GH_COPILOT_PAT }} | |
| run: | | |
| ISSUE_NUMBER="${{ steps.issue.outputs.number }}" | |
| echo "## 🤖 Invoking Copilot Coding Agent" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "Assigning issue #${ISSUE_NUMBER} to GitHub Copilot..." | tee -a $GITHUB_STEP_SUMMARY | |
| # Get issue details with retry | |
| MAX_RETRIES=3 | |
| RETRY_COUNT=0 | |
| BACKOFF=2 | |
| while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do | |
| if ISSUE_TITLE=$(gh issue view "${ISSUE_NUMBER}" --repo "${{ github.repository }}" --json title --jq '.title' 2>/dev/null) && \ | |
| ISSUE_BODY=$(gh issue view "${ISSUE_NUMBER}" --repo "${{ github.repository }}" --json body --jq '.body' 2>/dev/null); then | |
| break | |
| else | |
| RETRY_COUNT=$((RETRY_COUNT + 1)) | |
| if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then | |
| WAIT_TIME=$((BACKOFF ** RETRY_COUNT)) | |
| echo "⚠️ Failed to fetch issue details, retrying in ${WAIT_TIME}s (attempt $((RETRY_COUNT + 1))/${MAX_RETRIES})..." | tee -a $GITHUB_STEP_SUMMARY | |
| sleep $WAIT_TIME | |
| else | |
| echo "❌ Failed to fetch issue details after ${MAX_RETRIES} attempts" | tee -a $GITHUB_STEP_SUMMARY | |
| exit 1 | |
| fi | |
| fi | |
| done | |
| ISSUE_URL="https://github.com/${{ github.repository }}/issues/${ISSUE_NUMBER}" | |
| cat > /tmp/copilot_task_${ISSUE_NUMBER}.txt << TASK_EOF | |
| Fix ${ISSUE_TITLE} | |
| ${ISSUE_BODY} | |
| References: | |
| - Issue: ${ISSUE_URL} | |
| Please analyze this issue, identify the root cause, and implement a solution that: | |
| - Fixes the immediate problem | |
| - Prevents similar issues in the future | |
| - Includes proper error handling | |
| - Adds tests if applicable | |
| Ensure all tests pass and security scans are clean before finalizing. | |
| TASK_EOF | |
| # Create agent task with retry | |
| RETRY_COUNT=0 | |
| AGENT_CREATED=false | |
| while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do | |
| if AGENT_URL=$(gh agent-task create --repo "${{ github.repository }}" --base main -F /tmp/copilot_task_${ISSUE_NUMBER}.txt 2>&1); then | |
| echo "✅ Successfully created Copilot agent task" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "Agent session: ${AGENT_URL}" | tee -a $GITHUB_STEP_SUMMARY | |
| AGENT_CREATED=true | |
| break | |
| else | |
| RETRY_COUNT=$((RETRY_COUNT + 1)) | |
| if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then | |
| WAIT_TIME=$((BACKOFF ** RETRY_COUNT)) | |
| echo "⚠️ Failed to create agent task, retrying in ${WAIT_TIME}s (attempt $((RETRY_COUNT + 1))/${MAX_RETRIES})..." | tee -a $GITHUB_STEP_SUMMARY | |
| echo "Error: ${AGENT_URL}" | tee -a $GITHUB_STEP_SUMMARY | |
| sleep $WAIT_TIME | |
| else | |
| echo "❌ Failed to create Copilot agent task after ${MAX_RETRIES} attempts" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "Error: ${AGENT_URL}" | tee -a $GITHUB_STEP_SUMMARY | |
| rm -f /tmp/copilot_task_${ISSUE_NUMBER}.txt | |
| exit 1 | |
| fi | |
| fi | |
| done | |
| if [ "$AGENT_CREATED" = true ]; then | |
| # Update issue labels with retry | |
| RETRY_COUNT=0 | |
| while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do | |
| if gh issue edit "${ISSUE_NUMBER}" --repo "${{ github.repository }}" --remove-label "copilot-queued" --add-label "copilot-assigned" 2>/dev/null; then | |
| break | |
| else | |
| RETRY_COUNT=$((RETRY_COUNT + 1)) | |
| if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then | |
| WAIT_TIME=$((BACKOFF ** RETRY_COUNT)) | |
| echo "⚠️ Failed to update labels, retrying in ${WAIT_TIME}s..." | tee -a $GITHUB_STEP_SUMMARY | |
| sleep $WAIT_TIME | |
| fi | |
| fi | |
| done | |
| # Post comment with retry | |
| RETRY_COUNT=0 | |
| while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do | |
| if gh issue comment "${ISSUE_NUMBER}" --repo "${{ github.repository }}" --body "🤖 **Copilot Agent Task Created** | |
| A Copilot coding agent has been assigned to this issue. | |
| **Session URL**: ${AGENT_URL} | |
| The agent will: | |
| - Analyze the issue and related code | |
| - Create a new branch and pull request | |
| - Implement fixes autonomously | |
| - Run tests and security scans | |
| - Update the PR based on review feedback | |
| Track progress in the agent session logs and the PR that will be created." 2>/dev/null; then | |
| break | |
| else | |
| RETRY_COUNT=$((RETRY_COUNT + 1)) | |
| if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then | |
| WAIT_TIME=$((BACKOFF ** RETRY_COUNT)) | |
| echo "⚠️ Failed to post comment, retrying in ${WAIT_TIME}s..." | tee -a $GITHUB_STEP_SUMMARY | |
| sleep $WAIT_TIME | |
| fi | |
| fi | |
| done | |
| fi | |
| rm -f /tmp/copilot_task_${ISSUE_NUMBER}.txt | |
| - name: Summary | |
| if: always() | |
| run: | | |
| echo "" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "---" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "**Method**: \`gh agent-task create\` (Official GitHub CLI command)" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "**Requires**: GitHub CLI v2.80.0 or later" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "**Concurrency Limit**: 3 active agents maximum" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "**Cron Schedule**: Runs every 10 minutes to process queued issues" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "**Retry Policy**: 3 attempts with exponential backoff (2^n seconds)" | tee -a $GITHUB_STEP_SUMMARY |