From cd8b3daab0c18ea3a34c765bb2c97a2ae4e93367 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Fri, 10 Apr 2026 16:31:49 +1000 Subject: [PATCH 1/4] Improve error handling and update authorization method Refactored error handling and logging for invite process. Updated authorization method to use 'Bearer' token. --- .github/workflows/AutoInviteToOrgByStar.py | 67 +++++++++++++--------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/.github/workflows/AutoInviteToOrgByStar.py b/.github/workflows/AutoInviteToOrgByStar.py index ae0d791..9abbc43 100644 --- a/.github/workflows/AutoInviteToOrgByStar.py +++ b/.github/workflows/AutoInviteToOrgByStar.py @@ -1,10 +1,7 @@ #!/usr/bin/env python3 """ Auto-invite GitHub user to organization team when they star the repo. - -Author: Max Base -Sponsor: John Bampton -Date: 2025-05-09 +Hardened for production CI environments. """ import os @@ -12,11 +9,6 @@ import json import requests -def log_env_info(): - print("🔍 Environment Info:") - print("CI Environment:", "GitHub Actions" if os.getenv('CI') else "Local") - print("Python Prefix:", sys.prefix) - def load_event_data(): event_path = os.getenv('GITHUB_EVENT_PATH') if not event_path or not os.path.isfile(event_path): @@ -26,41 +18,60 @@ def load_event_data(): return json.load(file) def send_github_invite(username, team_id, token): + # Use the more modern /orgs/{org}/teams/{team_slug} if ID is problematic, + # but the provided ID-based URL works fine for legacy compatibility. url = f'https://api.github.com/teams/{team_id}/memberships/{username}' headers = { 'Accept': 'application/vnd.github.v3+json', - 'Authorization': f'token {token}' + 'Authorization': f'Bearer {token}' # 'Bearer' is the preferred modern standard over 'token' } - print(f"📨 Sending invite to @{username}...") - response = requests.put(url, headers=headers) - - if response.status_code == 200: - print("✅ User already a member.") - elif response.status_code == 201: - print("🎉 Invite sent successfully.") - else: - print(f"âš ī¸ Failed to send invite. Status Code: {response.status_code}") - print(response.text) + try: + print(f"📨 Processing invite for @{username}...") + response = requests.put(url, headers=headers, timeout=10) + + if response.status_code == 200: + print("✅ User is already a member or invitation is active.") + elif response.status_code == 201: + print("🎉 Invite sent successfully.") + elif response.status_code == 404: + print("âš ī¸ Team ID or User not found. Check your COMMUNITY_TEAM_ID.") + elif response.status_code == 403: + print("❌ Permission denied. Your MY_GITHUB_KEY may lack 'admin:org' scope.") + else: + # We avoid printing response.text to prevent secret leakage in logs + print(f"âš ī¸ API returned status: {response.status_code}") + + except requests.exceptions.RequestException as e: + print(f"❌ Network error: {e}") def main(): - print("👋 Hello, GitHub Actions!") + # Only log essential info to keep the log "clean" + if not os.getenv('GITHUB_ACTIONS'): + print("âš ī¸ Running outside of GitHub Actions.") - log_env_info() + # Validate inputs immediately + github_token = os.getenv('MY_GITHUB_KEY') + team_id = os.getenv('COMMUNITY_TEAM_ID') - try: - github_token = os.environ['MY_GITHUB_KEY'] - team_id = os.environ['COMMUNITY_TEAM_ID'] - except KeyError as e: - print(f"❌ Missing environment variable: {e}") + if not github_token or not team_id: + print("❌ Error: MY_GITHUB_KEY and COMMUNITY_TEAM_ID must be set.") sys.exit(1) try: event_data = load_event_data() + + # Verify this is actually a 'started' (star) action + action = event_data.get('action') + if action != 'started': + print(f"â„šī¸ Ignoring action type: {action}") + return + username = event_data['sender']['login'] send_github_invite(username, team_id, github_token) + except Exception as e: - print(f"❌ Error occurred: {e}") + print(f"❌ Script failed: {str(e)}") sys.exit(1) if __name__ == '__main__': From f63d3e367b5503e0b88a1d28b55e04cf10e5da77 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 06:37:09 +0000 Subject: [PATCH 2/4] Update 404 message to cover both invalid resource and insufficient permissions cases Agent-Logs-Url: https://github.com/metropolis-retro/.github/sessions/3ae8762c-9c1e-44e4-a834-69b46cf810a4 Co-authored-by: jbampton <418747+jbampton@users.noreply.github.com> --- .github/workflows/AutoInviteToOrgByStar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/AutoInviteToOrgByStar.py b/.github/workflows/AutoInviteToOrgByStar.py index 9abbc43..4011ac3 100644 --- a/.github/workflows/AutoInviteToOrgByStar.py +++ b/.github/workflows/AutoInviteToOrgByStar.py @@ -35,7 +35,8 @@ def send_github_invite(username, team_id, token): elif response.status_code == 201: print("🎉 Invite sent successfully.") elif response.status_code == 404: - print("âš ī¸ Team ID or User not found. Check your COMMUNITY_TEAM_ID.") + api_message = response.json().get('message', 'no details provided') + print(f"âš ī¸ Resource not found (404): {api_message}. This may indicate an invalid team/user or insufficient token permissions.") elif response.status_code == 403: print("❌ Permission denied. Your MY_GITHUB_KEY may lack 'admin:org' scope.") else: From f1c6e60fe215e660f614d63e5728954a3bd34794 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 06:37:49 +0000 Subject: [PATCH 3/4] Add error handling around response.json() to prevent JSONDecodeError Agent-Logs-Url: https://github.com/metropolis-retro/.github/sessions/3ae8762c-9c1e-44e4-a834-69b46cf810a4 Co-authored-by: jbampton <418747+jbampton@users.noreply.github.com> --- .github/workflows/AutoInviteToOrgByStar.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/AutoInviteToOrgByStar.py b/.github/workflows/AutoInviteToOrgByStar.py index 4011ac3..8c33b64 100644 --- a/.github/workflows/AutoInviteToOrgByStar.py +++ b/.github/workflows/AutoInviteToOrgByStar.py @@ -35,7 +35,10 @@ def send_github_invite(username, team_id, token): elif response.status_code == 201: print("🎉 Invite sent successfully.") elif response.status_code == 404: - api_message = response.json().get('message', 'no details provided') + try: + api_message = response.json().get('message', 'no details provided') + except ValueError: + api_message = 'no details provided' print(f"âš ī¸ Resource not found (404): {api_message}. This may indicate an invalid team/user or insufficient token permissions.") elif response.status_code == 403: print("❌ Permission denied. Your MY_GITHUB_KEY may lack 'admin:org' scope.") From c842bd09ef5b2f975a77cb03d6374e2e54bde2a9 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Fri, 10 Apr 2026 16:43:21 +1000 Subject: [PATCH 4/4] Update .github/workflows/AutoInviteToOrgByStar.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/AutoInviteToOrgByStar.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/AutoInviteToOrgByStar.py b/.github/workflows/AutoInviteToOrgByStar.py index 8c33b64..0b7caaa 100644 --- a/.github/workflows/AutoInviteToOrgByStar.py +++ b/.github/workflows/AutoInviteToOrgByStar.py @@ -41,7 +41,11 @@ def send_github_invite(username, team_id, token): api_message = 'no details provided' print(f"âš ī¸ Resource not found (404): {api_message}. This may indicate an invalid team/user or insufficient token permissions.") elif response.status_code == 403: - print("❌ Permission denied. Your MY_GITHUB_KEY may lack 'admin:org' scope.") + try: + api_message = response.json().get('message', 'no details provided') + except ValueError: + api_message = 'no details provided' + print(f"❌ Permission denied (403): {api_message}. This may indicate insufficient token permissions, SSO enforcement, or rate limiting.") else: # We avoid printing response.text to prevent secret leakage in logs print(f"âš ī¸ API returned status: {response.status_code}")