Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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')
")
Comment on lines +101 to +110
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The multi-line python3 -c snippet here includes leading indentation inside the quoted string (e.g., spaces before import). Python treats that as an IndentationError: unexpected indent, so this step will fail when it runs. Consider switching to a heredoc (python3 - <<'PY' ... PY) or make the -c string left-aligned with no leading spaces on each line.

Suggested change
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')
")
ALREADY_BYPASSED=$(echo "$RS_JSON" | python3 - <<PY
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')
PY
)

Copilot uses AI. Check for mistakes.

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))
")
Comment on lines +122 to +133
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

Same issue as above: this multi-line python3 -c payload builder is indented inside the quoted string, which will raise IndentationError and prevent the ruleset update from running. Use a heredoc / left-align the Python, or generate the payload with jq to avoid embedded multi-line Python in Bash strings.

Suggested change
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))
")
PAYLOAD=$(echo "$RS_JSON" | python3 -c "import json, sys; rs = json.load(sys.stdin); \
[rs.pop(key, None) for key in ('id','source_type','source','node_id','created_at','updated_at','_links','current_user_can_bypass')]; \
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))")

Copilot uses AI. Check for mistakes.

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"
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The manual remediation URL https://github.com/$REPO/rules/$RS_ID does not match GitHub’s ruleset settings paths (typically under /settings/rules). As written, this link is likely to 404 and won’t help users complete the manual bypass configuration.

Suggested change
echo " Manual fix: go to https://github.com/$REPO/rules/$RS_ID"
echo " Manual fix: go to https://github.com/$REPO/settings/rules/$RS_ID"

Copilot uses AI. Check for mistakes.
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
Expand Down
158 changes: 158 additions & 0 deletions .github/workflows/grant-copilot-bypass.yml
Original file line number Diff line number Diff line change
@@ -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"
Loading