Playbook: Worker GitHub Invitation Auto-Acceptance

When to use

Run this whenever a new GitHub repo is created and AI workers (AI-Dev-BMS4-1, AI-Dev-BMS4-2) were added as collaborators. Eliminates the human-action bottleneck from Step 6 of docs/playbooks/new-repo-setup.md.


Architecture (configured 2026-06-28)

The gh CLI on bms-4 runners is now authenticated as the respective AI-Dev account:

AccountHostLinux usergh CLI active accountgit identity
AI-Dev-BMS4-1bms-4 54.36.123.110claude-runnerAI-Dev-BMS4-1ai-dev-bms4-1@zintegrowana.online
AI-Dev-BMS4-2bms-4 54.36.123.110claude-runner-2ai-dev-bms4-2ai-dev-bms4-2@zintegrowana.online
AI-Dev-IO1vps-i1 217.154.82.162claude-runnerAI-Dev-IO1ai-dev-io1@zintegrowana.online

radieu is stored as secondary account on both runners (for owner-level ops when needed: gh auth switch -u radieu). Runners use GitHub OAuth tokens (device flow, no long-lived PATs).

Consequence: gh api /user/repository_invitations on a runner now returns that runner’s own pending invitations — acceptance works directly without PATs or Playwright.


Primary path — direct gh api on the runner

No PATs in SOPS needed. Runners use their own OAuth tokens.

Step 1 — Check pending invitations

# BMS4-1
ssh root@54.36.123.110 "su -s /bin/bash claude-runner   -c 'gh api /user/repository_invitations --jq \".[].repository.full_name\"'"
 
# BMS4-2
ssh root@54.36.123.110 "su -s /bin/bash claude-runner-2 -c 'gh api /user/repository_invitations --jq \".[].repository.full_name\"'"

Step 2 — Accept all pending invitations

# Write script to bms-4 and run:
cat > /tmp/accept-invites.sh << 'EOF'
#!/bin/bash
for USER in claude-runner claude-runner-2; do
  su -s /bin/bash $USER -c '
    for id in $(gh api /user/repository_invitations --jq ".[].id" 2>/dev/null); do
      gh api /user/repository_invitations/$id --method PATCH
      echo "Accepted $id as $(gh api /user --jq .login)"
    done
  '
done
EOF
ssh root@54.36.123.110 "bash /tmp/accept-invites.sh"

Step 3 — Verify

# Should show AI-Dev-BMS4-1 and ai-dev-bms4-2 alongside radieu:
gh api repos/radieu/<repo-name>/collaborators --jq ".[].login"

One-time setup — re-authenticating a runner as its AI-Dev account

Required when: setting up a new runner, OAuth token expires, or runner was re-imaged.

Uses GitHub’s device flow — no long-lived PATs stored.

On bms-4 via tmux

# Start session
ssh root@54.36.123.110 "tmux new-session -d -s ghauth -x 220 -y 50"
 
# For claude-runner (AI-Dev-BMS4-1):
ssh root@54.36.123.110 "tmux send-keys -t ghauth 'su -s /bin/bash claude-runner -c \"gh auth login --web -h github.com\"' Enter"
sleep 5
ssh root@54.36.123.110 "tmux send-keys -t ghauth '' Enter"  # select HTTPS
sleep 4
ssh root@54.36.123.110 "tmux capture-pane -t ghauth -p"
# → Shows: "First copy your one-time code: XXXX-XXXX"

User action:

  1. Open browser logged in as AI-Dev-BMS4-1
  2. Go to https://github.com/login/device
  3. Enter the displayed code
  4. Click “Authorize”
# Press Enter to complete the flow:
ssh root@54.36.123.110 "tmux send-keys -t ghauth '' Enter"
sleep 8
ssh root@54.36.123.110 "tmux capture-pane -t ghauth -p"
# → "✓ Logged in as AI-Dev-BMS4-1"
 
# Make AI-Dev the active (default) account:
ssh root@54.36.123.110 "su -s /bin/bash claude-runner -c 'gh auth switch -u AI-Dev-BMS4-1'"
 
# Repeat for claude-runner-2 / AI-Dev-BMS4-2

Note: The device code has a ~15 minute expiry. If it expires before authorization, restart the tmux session and generate a new code.


Fallback path — Playwright (when runner OAuth is unavailable)

Use only if the runner cannot complete the device flow (e.g., account locked, 2FA enabled).

ssh root@54.36.123.110 "pip3 install playwright && python3 -m playwright install chromium"
scp scripts/worker-github-accept-invitations.py root@54.36.123.110:/opt/p24-infra/scripts/
 
# Needs GH_PASSWORD_AI_DEV_BMS4_1 in bms-servers.env.sops
ssh root@54.36.123.110 "
  PASS=\$(sops --decrypt --input-type dotenv --output-type dotenv \
    /opt/p24-infra/secrets/bms-servers.env.sops 2>/dev/null \
    | grep '^GH_PASSWORD_AI_DEV_BMS4_1=' | cut -d= -f2-)
  GH_WORKER_PASSWORD='\$PASS' python3 /opt/p24-infra/scripts/worker-github-accept-invitations.py \
    --username AI-Dev-BMS4-1 --password-env GH_WORKER_PASSWORD
"

Integration with new-repo-setup.md (Step 6)

After sending collaboration invitations, accept directly from the runner:

ssh root@54.36.123.110 "
  sleep 5  # wait for GitHub to propagate
  for USER in claude-runner claude-runner-2; do
    su -s /bin/bash \$USER -c '
      for id in \$(gh api /user/repository_invitations --jq \".[].id\" 2>/dev/null); do
        gh api /user/repository_invitations/\$id --method PATCH
        echo \"Accepted \$id\"
      done
    '
  done
"

Verify both workers are registered in the platform

# Local dev (PostgREST API with service role key)
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$lines = sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops 2>$null
$svcKey = ($lines | Where-Object { $_ -match "^SUPABASE_ACCESS_TOKEN=" } | Select-Object -First 1) -replace "^SUPABASE_ACCESS_TOKEN=", ""
 
Invoke-RestMethod `
  "https://mwkqmgadqnkkihjdeqsi.supabase.co/rest/v1/dev_r_services?service_name=in.(AI-Dev-BMS4-1,AI-Dev-BMS4-2)&select=service_name,status" `
  -Headers @{Authorization="Bearer $svcKey"; apikey=$svcKey}

Expected: 2 rows, both status: active.

Result as of 2026-06-28:

AI-Dev-BMS4-1  active
AI-Dev-BMS4-2  active

Escalation

PAT returns 401: Token expired — repeat Step P1 & P2 for that account.

Playwright CAPTCHA: GitHub flagged the IP. Wait 24h. If persistent, consider using a GitHub App installation token instead of PATs.

Both paths fail: Fall back to human-action — create issue in p24-infra and have radieu log into the AI-Dev account and accept via browser.

AI-Dev-BMS4-2 gh auth stale: Currently claude-runner-2 has a stale OAuth session. This doesn’t affect this playbook (we use PATs, not gh OAuth), but for other operations re-auth is needed:

ssh root@54.36.123.110 "su -s /bin/bash claude-runner-2 -c 'gh auth login'"
# Follow interactive re-auth flow