This guide explains how to configure persistent GitHub CLI and Copilot CLI authentication on self-hosted GitHub Actions runners.
GitHub Actions workflows that use gh CLI commands, hosted Copilot task creation, or PR-comment automation need proper authentication. When running on self-hosted runners, the GITHUB_TOKEN provided by GitHub Actions workflows has limited scope and duration, which can cause failures in:
gh agent-task createcommands when the runner'sghbuild supports themgh pr create/gh issue createcommandsgh apicalls- Copilot CLI extension usage
Configure persistent, long-term GitHub CLI authentication on the self-hosted runner itself. This provides:
✅ Persistent Authentication - Survives across workflow runs and system reboots ✅ Full API Access - Uses a Personal Access Token with complete scopes ✅ Copilot Support - Enables GitHub CLI operations used by both hosted task creation and PR-comment automation ✅ Multiple User Support - Configures auth for both runner user and root ✅ Automatic Credential Helper - Git operations use gh authentication automatically
- Self-hosted runner installed and running
- GitHub Personal Access Token with these scopes:
repo- Full control of private repositoriesworkflow- Update GitHub Action workflowswrite:packages- Upload packages (optional)read:org- Read org and team membership
- Go to https://github.com/settings/tokens/new
- Give it a descriptive name:
Self-hosted Runner Auth - Set expiration (recommend "No expiration" for production runners)
- Select the scopes listed above
- Click "Generate token"
- Copy the token immediately (you won't see it again!)
# On the self-hosted runner machine
cd /path/to/ipfs_datasets_py
sudo ./scripts/setup_gh_copilot_auth_on_runner.shThe script will:
- Check if GitHub CLI is installed (installs if needed)
- Prompt for your GitHub Personal Access Token
- Configure GitHub CLI for runner and root users
- Install GitHub Copilot CLI extension
- Setup git credential helper
- Update runner service configuration
- Create verification script
# Run the verification script
sudo verify-gh-auth
# Or manually test
sudo -u runner gh auth status
sudo -u runner gh api user
sudo -u runner gh extension list# Find your runner service name
sudo systemctl list-units --type=service | grep actions.runner
# Restart it
sudo systemctl restart actions.runner.endomorphosis-ipfs_datasets_py.*.serviceFor runner user (/home/runner/.config/gh/hosts.yml):
github.com:
user: ""
oauth_token: ghp_xxxxxxxxxxxx
git_protocol: httpsFor root user (/root/.config/gh/hosts.yml):
github.com:
user: ""
oauth_token: ghp_xxxxxxxxxxxx
git_protocol: httpsFile: /home/actions-runner/.env
GH_TOKEN=ghp_xxxxxxxxxxxx
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
GIT_CONFIG_GLOBAL=/dev/null
GIT_CONFIG_SYSTEM=/dev/nullThe systemd service file is updated to load the environment file:
[Service]
EnvironmentFile=-/home/actions-runner/.envSystem-wide git configuration:
git config --system credential.helper "!gh auth git-credential"With authentication configured, workflows can use gh CLI commands without additional setup:
jobs:
assign-copilot:
runs-on: [self-hosted, linux, x64]
steps:
- name: Create Copilot Agent Task
run: |
# No GH_TOKEN needed - uses persistent auth!
gh agent-task create --repo ${{ github.repository }}jobs:
assign-copilot:
runs-on: [self-hosted, linux, x64]
steps:
- name: Trigger Copilot on an existing PR
run: |
python3 scripts/invoke_copilot_on_pr.py --pr "$PR_NUMBER" --repo ${{ github.repository }}jobs:
create-pr:
runs-on: [self-hosted, linux, x64]
steps:
- name: Create Draft PR
run: |
# Works with persistent auth
gh pr create --draft --title "Auto-fix" --body "..." For jobs running in containers, the workflow still needs to pass GH_TOKEN:
jobs:
container-job:
runs-on: [self-hosted, linux, x64]
container:
image: python:3.12-slim
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Still needed in containers
steps:
- name: Use gh CLI
run: gh auth status# Check if gh is authenticated
sudo -u runner gh auth status
# Check environment file exists
cat /home/actions-runner/.env
# Check service file includes environment
sudo cat /etc/systemd/system/actions.runner.*.service | grep EnvironmentFile
# Restart runner service
sudo systemctl restart actions.runner.*.service# Check if extension is installed
sudo -u runner gh extension list
# Install manually if needed
sudo -u runner gh extension install github/gh-copilot
# Upgrade extension
sudo -u runner gh extension upgrade gh-copilot# Remove existing auth
sudo -u runner gh auth logout
# Re-run setup script
sudo ./scripts/setup_gh_copilot_auth_on_runner.sh# Fix ownership of config directory
sudo chown -R runner:runner /home/runner/.config/gh
# Fix permissions
sudo chmod 700 /home/runner/.config/gh
sudo chmod 600 /home/runner/.config/gh/hosts.yml- ✅ Token is stored with restrictive permissions (600)
- ✅ Only runner user and root can read the token
- ✅ Token is stored in user home directory, not in repository
⚠️ Token has full repository access - protect the runner machine
- Use a dedicated GitHub account for runner authentication (e.g.,
my-org-bot) - Create organization-level tokens if managing multiple repositories
- Set token expiration and rotate regularly (or use no expiration with good security)
- Monitor token usage via GitHub's token usage page
- Restrict runner machine access - only trusted users should have SSH access
Minimum required scopes:
repo- For repository operationsworkflow- For workflow modifications
Optional but useful:
write:packages- If using GitHub Packagesread:org- If querying organization dataread:discussion- If interacting with discussions
For multiple runners, run the setup script on each:
# On runner 1
sudo ./scripts/setup_gh_copilot_auth_on_runner.sh
# On runner 2
sudo ./scripts/setup_gh_copilot_auth_on_runner.sh
# Each can use the same token or different tokensIf your runner uses a different username:
# Set custom runner user
export RUNNER_USER=my-custom-runner-user
sudo ./scripts/setup_gh_copilot_auth_on_runner.shCreate a script to update tokens automatically:
#!/bin/bash
# rotate-gh-token.sh
NEW_TOKEN="$1"
RUNNER_USER="runner"
# Update hosts.yml files
sudo sed -i "s/oauth_token: .*/oauth_token: ${NEW_TOKEN}/" \
/home/${RUNNER_USER}/.config/gh/hosts.yml \
/root/.config/gh/hosts.yml
# Update environment file
sudo sed -i "s/GH_TOKEN=.*/GH_TOKEN=${NEW_TOKEN}/" \
/home/actions-runner/.env
sudo sed -i "s/GITHUB_TOKEN=.*/GITHUB_TOKEN=${NEW_TOKEN}/" \
/home/actions-runner/.env
# Restart runner
sudo systemctl restart actions.runner.*.service
echo "Token rotated successfully"Even with persistent auth configured, our workflow scripts include fallbacks:
- Primary: Use persistent runner authentication
- Fallback 1: Use workflow
GITHUB_TOKENenvironment variable - Fallback 2: Use
gh pr commentwith@copilotmention instead ofgh agent-task
This ensures workflows continue working even if runner authentication fails.
- GitHub CLI Authentication
- GitHub Copilot CLI
- GitHub Actions Self-Hosted Runners
- GitHub Personal Access Tokens
- Copilot Coding Agent
If you encounter issues:
- Run the verification script:
sudo verify-gh-auth - Check workflow logs for specific errors
- Review runner service logs:
sudo journalctl -u actions.runner.*.service -n 100 - Verify token scopes on GitHub: https://github.com/settings/tokens