Skip to content

Instantly share code, notes, and snippets.

@cprima
Last active April 27, 2025 09:36
Show Gist options
  • Save cprima/8a5f8e9545f9d22d8de7fcab0eb1c179 to your computer and use it in GitHub Desktop.
Save cprima/8a5f8e9545f9d22d8de7fcab0eb1c179 to your computer and use it in GitHub Desktop.
GitHub Actions Auto-Onboarding: Based on an Issue Template, Invite users to org, Add to teams, Comment results, Label failures, and Close the issue automatically.

πŸ› οΈ Auto-Onboarding via GitHub Issues

This repository automates inviting new members to the GitHub organization and adding them to teams β€” via simply opening an issue.

πŸš€ How it Works

  1. A user opens a new issue and fills the onboarding template (GitHub Username + Agreement).
  2. A GitHub Action triggers automatically:
    • Extracts the GitHub username.
    • Checks if the user already belongs to the organization.
    • Invites the user if necessary.
    • Adds the user to configured teams.
    • Comments back on the issue.
    • Applies a failure label if something went wrong.
    • Closes the issue automatically if onboarding is successful.

πŸ—ΊοΈ Decision Flow

User opens issue
    β”‚
    β”œβ”€β”€ Is issue valid? (Username + Agreement)
    β”‚     β”œβ”€β”€ No β†’ Comment error and STOP
    β”‚     └── Yes
    β”‚
    β”œβ”€β”€ Is user already in organization?
    β”‚     β”œβ”€β”€ No β†’ Invite user
    β”‚     └── Yes
    β”‚
    β”œβ”€β”€ Is user in all required teams?
    β”‚     β”œβ”€β”€ Yes β†’ Comment success β†’ Close issue
    β”‚     └── No
    β”‚
    β”œβ”€β”€ Try adding to missing teams
    β”‚
    β”œβ”€β”€ Were all team additions successful?
    β”‚     β”œβ”€β”€ Yes β†’ Comment success β†’ Close issue
    β”‚     └── No β†’ Comment warning, Add failure label, Do not close

βš™οΈ Configuration

Settings are stored inside .github/onboarding-config.yml:

Key Purpose
org_name GitHub organization name
team_list Comma-separated list of team slugs to add users to
pat_secret_name Name of the GitHub Secret with Admin PAT
mention_team GitHub Team to mention on failures
failure_label Label to apply to issues if onboarding partially fails

Example:

org_name: "rpapub"
team_list: "developers,qa-team"
pat_secret_name: "ORG_RPAPUB_ADMIN_PAT"
mention_team: "@global-owners"
failure_label: "invalid"

πŸ”’ Secrets and Permissions

Required Secret

You must create a repository secret that contains a GitHub Personal Access Token (PAT).

Secret name Source
Defined in config under pat_secret_name e.g., ORG_MYORGNAME_ADMIN_PAT

This secret is used by the workflow to authenticate API requests (inviting users, adding team memberships, commenting, etc).

How to create the secret

  1. Go to your repository β†’ Settings β†’ Secrets β†’ Actions
  2. Click New repository secret
  3. Name it according to your onboarding-config.yml (pat_secret_name field).
  4. Paste the Personal Access Token (PAT) as the value.

Personal Access Token (PAT) scopes

The PAT must have at minimum:

Scope Why needed
admin:org To invite users to the organization
write:org To add users to teams
repo (optional) To post comments on issues (public repos usually work without this)

βœ… Recommendation:
Generate a fine-scoped token just for this workflow with only necessary permissions.

πŸ“‹ Notes

  • Team slugs must match GitHub URL slugs (e.g., "QA Team" β†’ qa-team).
  • PAT must have admin:org and write:org permissions.
  • Failure labeling is optional β€” set failure_label in config to enable.
org_name: "example-org"
team_list: "developers,qa-team"
pat_secret_name: "ORG_EXAMPLE_ADMIN_PAT"
mention_team: "@example-org/owners"
failure_label: "invalid"
name: Auto-Invite New Member
on:
issues:
types: [opened]
jobs:
invite:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Load configuration
id: load_config
run: |
ORG_NAME=$(yq '.org_name' .github/onboarding-config.yml)
TEAM_LIST=$(yq '.team_list' .github/onboarding-config.yml)
PAT_SECRET_NAME=$(yq '.pat_secret_name' .github/onboarding-config.yml)
MENTION_TEAM=$(yq '.mention_team' .github/onboarding-config.yml)
FAILURE_LABEL=$(yq '.failure_label' .github/onboarding-config.yml)
echo "ORG_NAME=$ORG_NAME" >> $GITHUB_ENV
echo "TEAM_LIST=$TEAM_LIST" >> $GITHUB_ENV
echo "PAT_SECRET_NAME=$PAT_SECRET_NAME" >> $GITHUB_ENV
echo "MENTION_TEAM=$MENTION_TEAM" >> $GITHUB_ENV
echo "FAILURE_LABEL=$FAILURE_LABEL" >> $GITHUB_ENV
- name: Check if issue is valid onboarding request
id: check
run: |
BODY="${{ github.event.issue.body }}"
if echo "$BODY" | grep -q "GitHub Username" && echo "$BODY" | grep -q "I agree to follow"; then
echo "valid_request=true" >> $GITHUB_ENV
else
echo "::error ::Issue missing required fields. Exiting."
echo "valid_request=false" >> $GITHUB_ENV
fi
- name: Extract GitHub Username
if: env.valid_request == 'true'
id: extract
run: |
BODY="${{ github.event.issue.body }}"
USERNAME=$(echo "$BODY" | awk '/### GitHub Username/ {flag=1; next} flag && NF {print; exit}' | xargs)
if [ -z "$USERNAME" ]; then
echo "::error ::No username found after parsing. Exiting."
exit 1
fi
echo "USERNAME=$USERNAME" >> $GITHUB_ENV
echo "username=$USERNAME" >> $GITHUB_OUTPUT
- name: Lookup GitHub user ID
if: env.valid_request == 'true'
id: lookup
run: |
RESPONSE=$(curl -s -H "Authorization: Bearer ${{ secrets[env.PAT_SECRET_NAME] }}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/users/${{ env.USERNAME }})
USER_ID=$(echo "$RESPONSE" | jq '.id // empty')
if [ -z "$USER_ID" ]; then
echo "::error ::GitHub user not found. Exiting."
exit 1
fi
echo "Resolved USER_ID: $USER_ID"
echo "USER_ID=$USER_ID" >> $GITHUB_ENV
echo "user_id=$USER_ID" >> $GITHUB_OUTPUT
- name: Check org and team membership
if: env.valid_request == 'true'
id: check_membership
run: |
echo "Checking if @${{ env.USERNAME }} is already fully onboarded..."
ORG_MEMBER=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${{ secrets[env.PAT_SECRET_NAME] }}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/orgs/${{ env.ORG_NAME }}/members/${{ env.USERNAME }})
ALL_TEAMS_OK=true
IFS=',' read -ra TEAMS <<< "${{ env.TEAM_LIST }}"
for TEAM in "${TEAMS[@]}"; do
TEAM=$(echo "$TEAM" | xargs)
TEAM_MEMBER=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${{ secrets[env.PAT_SECRET_NAME] }}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/orgs/${{ env.ORG_NAME }}/teams/${TEAM}/memberships/${{ env.USERNAME }})
if [ "$TEAM_MEMBER" -ne 200 ]; then
echo "❌ Not in team: $TEAM"
ALL_TEAMS_OK=false
else
echo "βœ… Already in team: $TEAM"
fi
done
if [ "$ORG_MEMBER" -eq 204 ] && [ "$ALL_TEAMS_OK" == "true" ]; then
echo "βœ… Fully onboarded."
echo "already_fully_member=true" >> $GITHUB_ENV
else
echo "🚧 Needs onboarding."
echo "already_fully_member=false" >> $GITHUB_ENV
fi
- name: Invite to organization
if: env.valid_request == 'true' && env.already_fully_member == 'false'
run: |
ORG_MEMBER=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${{ secrets[env.PAT_SECRET_NAME] }}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/orgs/${{ env.ORG_NAME }}/members/${{ env.USERNAME }})
if [ "$ORG_MEMBER" -ne 204 ]; then
echo "Inviting USER_ID=${{ env.USER_ID }} to organization..."
curl -X POST \
-H "Authorization: Bearer ${{ secrets[env.PAT_SECRET_NAME] }}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/orgs/${{ env.ORG_NAME }}/invitations \
-d "{\"invitee_id\": ${{ env.USER_ID }}}"
else
echo "Already in organization."
fi
- name: Add to teams
if: env.valid_request == 'true' && env.already_fully_member == 'false'
id: add_teams
run: |
team_add_failed=false
echo "Adding @${{ env.USERNAME }} to required teams..."
IFS=',' read -ra TEAMS <<< "${{ env.TEAM_LIST }}"
for TEAM in "${TEAMS[@]}"; do
TEAM=$(echo "$TEAM" | xargs)
TEAM_EXISTS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${{ secrets[env.PAT_SECRET_NAME] }}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/orgs/${{ env.ORG_NAME }}/teams/${TEAM})
if [ "$TEAM_EXISTS" -ne 200 ]; then
echo "::warning ::Team $TEAM does not exist. Skipping."
team_add_failed=true
continue
fi
TEAM_MEMBER=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${{ secrets[env.PAT_SECRET_NAME] }}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/orgs/${{ env.ORG_NAME }}/teams/${TEAM}/memberships/${{ env.USERNAME }})
if [ "$TEAM_MEMBER" -ne 200 ]; then
ADD_RESPONSE=$(curl -s -w "%{http_code}" -o /dev/null \
-X PUT \
-H "Authorization: Bearer ${{ secrets[env.PAT_SECRET_NAME] }}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/orgs/${{ env.ORG_NAME }}/teams/${TEAM}/memberships/${{ env.USERNAME }})
if [ "$ADD_RESPONSE" -ne 200 ] && [ "$ADD_RESPONSE" -ne 201 ]; then
echo "::warning ::Failed to add ${{ env.USERNAME }} to team $TEAM"
team_add_failed=true
else
echo "Added user to team: $TEAM"
fi
else
echo "Already in team: $TEAM"
fi
done
echo "team_add_failed=$team_add_failed" >> $GITHUB_ENV
- name: Comment back on issue
if: env.valid_request == 'true'
run: |
if [ "${{ env.already_fully_member }}" == "true" ]; then
COMMENT_BODY="βœ… @${{ env.USERNAME }} is already a full member (organization + teams)!"
elif [ "${{ env.team_add_failed }}" == "true" ]; then
COMMENT_BODY="⚠️ ${{ env.MENTION_TEAM }} Attention needed: @${{ env.USERNAME }} was invited, but some team additions failed."
else
COMMENT_BODY="πŸŽ‰ @${{ env.USERNAME }} has been invited and added to required teams! Welcome aboard!"
fi
curl -X POST \
-H "Authorization: Bearer ${{ secrets[env.PAT_SECRET_NAME] }}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments \
-d "{\"body\":\"$COMMENT_BODY\"}"
- name: Add failure label if configured
if: env.valid_request == 'true' && env.team_add_failed == 'true' && env.FAILURE_LABEL != ''
run: |
echo "Adding failure label '${{ env.FAILURE_LABEL }}'..."
curl -X POST \
-H "Authorization: Bearer ${{ secrets[env.PAT_SECRET_NAME] }}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/labels \
-d "{\"labels\": [\"${{ env.FAILURE_LABEL }}\"]}"
- name: Close the issue
if: env.valid_request == 'true'
run: |
if [ "${{ env.team_add_failed }}" != "true" ]; then
curl -X PATCH \
-H "Authorization: Bearer ${{ secrets[env.PAT_SECRET_NAME] }}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }} \
-d '{"state":"closed"}'
else
echo "Not closing because team additions failed."
fi

GitHub Username

example-user

Email (optional)

[email protected]

Requested Teams

developers, qa-team

Agreement

  • I agree to follow the organization's Code of Conduct.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment