diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 6c8d86967..4e493ae66 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -23,6 +23,9 @@ jobs: # Set the permissions to the lowest permissions possible needed for your steps. # Copilot will be given its own token for its operations. + # Note: The ruleset bypass step below uses COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN + # (PAT) directly — not the GITHUB_TOKEN — because the GITHUB_TOKEN cannot be granted + # 'administration' scope via the permissions block in GitHub Actions. permissions: contents: read actions: read @@ -42,6 +45,111 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + # Grant Copilot SWE agent bypass permissions on all repository rulesets. + # The Copilot SWE agent (GitHub App ID 1143301) needs pull_request bypass on + # rulesets that include the default branch (e.g. copilot_code_review rules) + # so it can create PRs without being blocked. This step is idempotent. + - name: Grant Copilot agent bypass permissions on branch rulesets + env: + GH_TOKEN: ${{ secrets.COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN }} + run: | + set -euo pipefail + REPO="${{ github.repository }}" + COPILOT_SWE_APP_ID=1143301 + + echo "🔒 Fetching repository rulesets for $REPO..." + RULESETS=$(curl -sf \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$REPO/rulesets" 2>&1) || { + echo "âš ī¸ Could not fetch rulesets (token may lack administration scope) — skipping" + exit 0 + } + + # Check if jq is available; fall back to Python + if ! command -v jq &>/dev/null; then + echo "â„šī¸ jq not found, using Python for JSON processing" + RULESET_IDS=$(echo "$RULESETS" | python3 -c " + import json, sys + rs = json.load(sys.stdin) + if isinstance(rs, list): + for r in rs: + print(r['id']) + ") + else + RULESET_IDS=$(echo "$RULESETS" | jq -r '.[].id // empty') + fi + + if [ -z "$RULESET_IDS" ]; then + echo "â„šī¸ No rulesets found — nothing to update" + exit 0 + fi + + for RS_ID in $RULESET_IDS; do + echo "📋 Checking ruleset ID $RS_ID..." + RS_JSON=$(curl -sf \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$REPO/rulesets/$RS_ID") || { + echo "âš ī¸ Could not fetch ruleset $RS_ID — skipping" + continue + } + + # Check if bypass actor already present + ALREADY_BYPASSED=$(echo "$RS_JSON" | python3 -c " + import json, sys + rs = json.load(sys.stdin) + actors = rs.get('bypass_actors', []) + found = any( + str(a.get('actor_id')) == '$COPILOT_SWE_APP_ID' and a.get('actor_type') == 'Integration' + for a in actors + ) + print('yes' if found else 'no') + ") + + RS_NAME=$(echo "$RS_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('name','unknown'))") + + if [ "$ALREADY_BYPASSED" = "yes" ]; then + echo " ✅ '$RS_NAME': Copilot SWE agent bypass already configured" + continue + fi + + echo " 🔧 Adding Copilot SWE agent bypass to '$RS_NAME'..." + + # Build updated payload: keep existing fields, add bypass actor + PAYLOAD=$(echo "$RS_JSON" | python3 -c " + import json, sys + rs = json.load(sys.stdin) + # Remove read-only fields that cannot be sent back + for key in ('id','source_type','source','node_id','created_at','updated_at','_links','current_user_can_bypass'): + rs.pop(key, None) + # Add bypass actor + actors = rs.get('bypass_actors', []) + actors.append({'actor_id': $COPILOT_SWE_APP_ID, 'actor_type': 'Integration', 'bypass_mode': 'pull_request'}) + rs['bypass_actors'] = actors + print(json.dumps(rs)) + ") + + RESULT=$(curl -sf -X PUT \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "Content-Type: application/json" \ + "https://api.github.com/repos/$REPO/rulesets/$RS_ID" \ + -d "$PAYLOAD" 2>&1) && { + UPDATED_BYPASS_COUNT=$(echo "$RESULT" | python3 -c "import json,sys; r=json.load(sys.stdin); print(len(r.get('bypass_actors',[])))" 2>/dev/null || echo "?") + echo " ✅ '$RS_NAME': bypass granted (total bypass actors: $UPDATED_BYPASS_COUNT)" + } || { + echo " âš ī¸ Could not update ruleset '$RS_NAME' (token may lack administration scope)" + echo " Manual fix: go to https://github.com/$REPO/rules/$RS_ID" + echo " Add 'GitHub Apps > copilot-swe-agent' to the Bypass list" + } + done + + echo "🎉 Ruleset bypass check complete" + # Cache APT packages for faster installs - name: Cache APT packages uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 diff --git a/.github/workflows/grant-copilot-bypass.yml b/.github/workflows/grant-copilot-bypass.yml new file mode 100644 index 000000000..4c2ac4577 --- /dev/null +++ b/.github/workflows/grant-copilot-bypass.yml @@ -0,0 +1,158 @@ +name: Grant Copilot Agent Bypass Permissions + +# Run manually from the Actions tab to add the Copilot SWE agent +# (App ID 1143301) as a bypass actor on all repository rulesets. +# +# PREREQUISITE: COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN must be a classic PAT +# with 'repo' scope OR a fine-grained PAT with "Administration: Read & write" +# repository permission. Without this, the workflow will print instructions +# for manual configuration. +# +# Trigger: Actions tab → "Grant Copilot Agent Bypass Permissions" → Run workflow +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + grant-bypass: + name: Add Copilot SWE agent to ruleset bypass list + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 + with: + egress-policy: audit + + - name: Grant bypass permissions on all branch rulesets + env: + GH_TOKEN: ${{ secrets.COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN }} + REPO: ${{ github.repository }} + COPILOT_SWE_APP_ID: "1143301" + run: | + set -euo pipefail + + echo "🔒 Fetching repository rulesets for $REPO..." + RULESETS=$(curl -sf \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$REPO/rulesets") || { + echo "âš ī¸ Could not fetch rulesets" + exit 0 + } + + RULESET_COUNT=$(echo "$RULESETS" | jq 'length') + echo "Found $RULESET_COUNT ruleset(s)" + + if [ "$RULESET_COUNT" -eq 0 ]; then + echo "â„šī¸ No rulesets found — nothing to update" + exit 0 + fi + + UPDATED=0 + ALREADY_OK=0 + NEEDS_MANUAL=0 + + while IFS= read -r RS_ID; do + RS_NAME=$(echo "$RULESETS" | jq -r --argjson id "$RS_ID" '.[] | select(.id == $id) | .name') + echo "" + echo "📋 Ruleset: '$RS_NAME' (ID: $RS_ID)" + + FULL_RS=$(curl -sf \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$REPO/rulesets/$RS_ID") || { + echo " âš ī¸ Could not fetch ruleset $RS_ID — skipping" + NEEDS_MANUAL=$((NEEDS_MANUAL + 1)) + continue + } + + HAS_BYPASS=$(echo "$FULL_RS" | jq \ + --argjson app_id "$COPILOT_SWE_APP_ID" \ + '[.bypass_actors[] | select(.actor_id == $app_id and .actor_type == "Integration")] | length > 0') + + if [ "$HAS_BYPASS" = "true" ]; then + echo " ✅ Copilot SWE agent bypass already configured" + ALREADY_OK=$((ALREADY_OK + 1)) + continue + fi + + echo " 🔧 Adding Copilot SWE agent bypass..." + + PAYLOAD=$(echo "$FULL_RS" | jq \ + --argjson app_id "$COPILOT_SWE_APP_ID" \ + 'del(.id, .source_type, .source, .node_id, .created_at, .updated_at, ._links, .current_user_can_bypass) + | .bypass_actors += [{"actor_id": $app_id, "actor_type": "Integration", "bypass_mode": "pull_request"}]') + + HTTP_STATUS=$(curl -s -o /tmp/rs_update_response.json -w "%{http_code}" -X PUT \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "Content-Type: application/json" \ + "https://api.github.com/repos/$REPO/rulesets/$RS_ID" \ + -d "$PAYLOAD") + + if [ "$HTTP_STATUS" -eq 200 ]; then + NEW_COUNT=$(cat /tmp/rs_update_response.json | jq '.bypass_actors | length') + echo " ✅ Bypass granted (total bypass actors: $NEW_COUNT)" + UPDATED=$((UPDATED + 1)) + else + ERROR_MSG=$(cat /tmp/rs_update_response.json | jq -r '.message // "Unknown error"') + echo " âš ī¸ Could not update ruleset automatically (HTTP $HTTP_STATUS: $ERROR_MSG)" + echo " The COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN needs 'Administration: Read & write'" + echo " permission (for fine-grained PAT) or 'repo' scope (for classic PAT)." + NEEDS_MANUAL=$((NEEDS_MANUAL + 1)) + fi + + done < <(echo "$RULESETS" | jq -r '.[].id') + + echo "" + echo "================================" + echo "✅ Updated: $UPDATED ruleset(s)" + echo "✅ Already OK: $ALREADY_OK ruleset(s)" + + if [ "$NEEDS_MANUAL" -gt 0 ]; then + echo "" + echo "âš ī¸ $NEEDS_MANUAL ruleset(s) require manual configuration." + echo "" + echo "MANUAL FIX (takes ~30 seconds):" + echo "1. Go to: https://github.com/$REPO/settings/rules" + echo "2. Click 'Copilot review for default branch'" + echo "3. Under 'Bypass list', click '+ Add bypass'" + echo "4. Select 'GitHub Apps' → search for 'copilot-swe-agent'" + echo "5. Select 'Pull requests' bypass mode" + echo "6. Save" + echo "" + echo "OR update COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN to a classic PAT" + echo "with 'repo' scope, then re-run this workflow." + echo "" + echo "After the fix, re-assign each blocked issue to the Copilot agent." + fi + + if [ "$UPDATED" -gt 0 ]; then + echo "🎉 Rulesets updated successfully!" + fi + + - name: Summary + if: always() + run: | + echo "## Copilot Agent Bypass Grant Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "### Action Required" >> "$GITHUB_STEP_SUMMARY" + echo "If the step above shows âš ī¸ warnings, the PAT lacks Administration permission." >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "**Quick fix (web UI):**" >> "$GITHUB_STEP_SUMMARY" + echo "1. Go to [Repository Rulesets](https://github.com/${{ github.repository }}/settings/rules)" >> "$GITHUB_STEP_SUMMARY" + echo "2. Click **Copilot review for default branch**" >> "$GITHUB_STEP_SUMMARY" + echo "3. Under **Bypass list** → **Add bypass** → **GitHub Apps** → **copilot-swe-agent**" >> "$GITHUB_STEP_SUMMARY" + echo "4. Select **Pull requests** mode → Save" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "**Or update the PAT** (Settings → Developer settings → Personal access tokens):" >> "$GITHUB_STEP_SUMMARY" + echo "Add **Administration: Read & write** to \`COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN\`" >> "$GITHUB_STEP_SUMMARY" + echo "then re-run this workflow." >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "After either fix, re-assign the Copilot agent to each blocked issue." >> "$GITHUB_STEP_SUMMARY"