---
name: github-auth
description: "GitHub auth setup: HTTPS tokens, SSH keys, gh CLI login."
version: 1.1.0
author: Hermes Agent
license: MIT
platforms: [linux, macos, windows]
metadata:
  hermes:
    tags: [GitHub, Authentication, Git, gh-cli, SSH, Setup]
    related_skills: [github-pr-workflow, github-code-review, github-issues, github-repo-management]
---

# GitHub Authentication Setup

This skill sets up authentication so the agent can work with GitHub repositories, PRs, issues, and CI. It covers two paths:

- **`git` (always available)** — uses HTTPS personal access tokens or SSH keys
- **`gh` CLI (if installed)** — richer GitHub API access with a simpler auth flow

## Detection Flow

When a user asks you to work with GitHub, run this check first:

```bash
# Check what's available
git --version
gh --version 2>/dev/null || echo "gh not installed"

# Check if already authenticated
gh auth status 2>/dev/null || echo "gh not authenticated"
git config --global credential.helper 2>/dev/null || echo "no git credential helper"
```

**Decision tree:**
1. If `gh auth status` shows authenticated → you're good, use `gh` for everything
2. If `gh` is installed but not authenticated → use "gh auth" method below
3. If `gh` is not installed → use "git-only" method below (no sudo needed)

---

## Method 1: Git-Only Authentication (No gh, No sudo)

This works on any machine with `git` installed. No root access needed.

### Option A: HTTPS with Personal Access Token (Recommended)

This is the most portable method — works everywhere, no SSH config needed.

**Step 1: Create a personal access token**

Tell the user to go to: **https://github.com/settings/tokens**

- Click "Generate new token (classic)"
- Give it a name like "hermes-agent"
- Select scopes:
  - `repo` (full repository access — read, write, push, PRs)
  - `workflow` (trigger and manage GitHub Actions)
  - `read:org` (if working with organization repos)
- Set expiration (90 days is a good default)
- Copy the token — it won't be shown again

**Step 2: Configure git to store the token**

```bash
# Set up the credential helper to cache credentials
# "store" saves to ~/.git-credentials in plaintext (simple, persistent)
git config --global credential.helper store

# Now do a test operation that triggers auth — git will prompt for credentials
# Username: <their-github-username>
# Password: <paste the personal access token, NOT their GitHub password>
git ls-remote https://github.com/<their-username>/<any-repo>.git
```

After entering credentials once, they're saved and reused for all future operations.

**Alternative: cache helper (credentials expire from memory)**

```bash
# Cache in memory for 8 hours (28800 seconds) instead of saving to disk
git config --global credential.helper 'cache --timeout=28800'
```

**Alternative: set the token directly in the remote URL (per-repo)**

```bash
# Embed token in the remote URL (avoids credential prompts entirely)
git remote set-url origin https://<username>:<token>@github.com/<owner>/<repo>.git
```

**Step 3: Configure git identity**

```bash
# Required for commits — set name and email
git config --global user.name "Their Name"
git config --global user.email "their-email@example.com"
```

**Step 4: Verify**

```bash
# Test push access (this should work without any prompts now)
git ls-remote https://github.com/<their-username>/<any-repo>.git

# Verify identity
git config --global user.name
git config --global user.email
```

### Option B: SSH Key Authentication

Good for users who prefer SSH or already have keys set up.

**Step 1: Check for existing SSH keys**

```bash
ls -la ~/.ssh/id_*.pub 2>/dev/null || echo "No SSH keys found"
```

**Step 2: Generate a key if needed**

```bash
# Generate an ed25519 key (modern, secure, fast)
ssh-keygen -t ed25519 -C "their-email@example.com" -f ~/.ssh/id_ed25519 -N ""

# Display the public key for them to add to GitHub
cat ~/.ssh/id_ed25519.pub
```

Tell the user to add the public key at: **https://github.com/settings/keys**
- Click "New SSH key"
- Paste the public key content
- Give it a title like "hermes-agent-<machine-name>"

**Step 3: Test the connection**

```bash
ssh -T git@github.com
# Expected: "Hi <username>! You've successfully authenticated..."
```

**Step 4: Configure git to use SSH for GitHub**

```bash
# Rewrite HTTPS GitHub URLs to SSH automatically
git config --global url."git@github.com:".insteadOf "https://github.com/"
```

**Step 5: Configure git identity**

```bash
git config --global user.name "Their Name"
git config --global user.email "their-email@example.com"
```

---

## Method 2: gh CLI Authentication

If `gh` is installed, it handles both API access and git credentials in one step.

### Interactive Browser Login (Desktop)

```bash
gh auth login
# Select: GitHub.com
# Select: HTTPS
# Authenticate via browser
```

### Token-Based Login (Headless / SSH Servers)

```bash
echo "<THEIR_TOKEN>" | gh auth login --with-token

# Set up git credentials through gh
gh auth setup-git
```

### Verify

```bash
gh auth status
```

---

## Using the GitHub API Without gh

When `gh` is not available, you can still access the full GitHub API using `curl` with a personal access token. This is how the other GitHub skills implement their fallbacks.

### Setting the Token for API Calls

```bash
# Option 1: Export as env var (preferred — keeps it out of commands)
export GITHUB_TOKEN="<token>"

# Then use in curl calls:
curl -s -H "Authorization: token $GITHUB_TOKEN" \
  https://api.github.com/user
```

### Extracting the Token from Git Credentials

If git credentials are already configured (via credential.helper store), the token can be extracted:

```bash
# Read from git credential store
grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|'
```

### Helper: Detect Auth Method

Use this pattern at the start of any GitHub workflow:

```bash
# Try gh first, fall back to git + curl
if command -v gh &>/dev/null && gh auth status &>/dev/null; then
  echo "AUTH_METHOD=gh"
elif [ -n "$GITHUB_TOKEN" ]; then
  echo "AUTH_METHOD=curl"
elif [ -f ~/.hermes/.env ] && grep -q "^GITHUB_TOKEN=" ~/.hermes/.env; then
  export GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" ~/.hermes/.env | head -1 | cut -d= -f2 | tr -d '\n\r')
  echo "AUTH_METHOD=curl"
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
  export GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
  echo "AUTH_METHOD=curl"
else
  echo "AUTH_METHOD=none"
  echo "Need to set up authentication first"
fi
```

---

## Troubleshooting

| Problem | Solution |
|---------|----------|
| `git push` asks for password | GitHub disabled password auth. Use a personal access token as the password, or switch to SSH |
| `remote: Permission to X denied` | Token may lack `repo` scope — regenerate with correct scopes |
| `fatal: Authentication failed` | Cached credentials may be stale — run `git credential reject` then re-authenticate |
| `ssh: connect to host github.com port 22: Connection refused` | Try SSH over HTTPS port: add `Host github.com` with `Port 443` and `Hostname ssh.github.com` to `~/.ssh/config` |
| Credentials not persisting | Check `git config --global credential.helper` — must be `store` or `cache` |
| Multiple GitHub accounts | Use SSH with different keys per host alias in `~/.ssh/config`, or per-repo credential URLs |
| `gh: command not found` + no sudo | Use git-only Method 1 above — no installation needed |
| `gh` is logged in as wrong user (e.g. agent's own user, not the human's) | See **Publishing on Behalf of Another User** below — use a per-command `GH_TOKEN` override |

## Publishing on Behalf of Another User

**This is the most important pattern this skill covers for agentic workflows.** When the agent itself is authenticated as user A (e.g. via its own `gh auth login` or its own PAT) but the human you're helping is user B and wants the work published under B's identity, the two things have to be set up correctly and they are NOT the same thing:

- **Authentication to GitHub** is done by the **PAT** (or `gh` session). The PAT determines whose quota you burn and whose API rate limit applies.
- **Attribution of commits** is done by **`git config user.name` / `user.email`** in the working repo. These determine what shows up on GitHub as the author of each commit — regardless of who pushed them.

These can be different people, and they need to be. If you push user B's work while the local `git config user.email` is still your agent email, every commit will show as authored by you and B's contribution graph will look like a bot, not a human.

### Step-by-step

```bash
# 1. Get the human's PAT (NOT yours). Scope: repo (and workflow if needed).
#    Have them generate it at https://github.com/settings/tokens
#    and paste it to you once, then you discard it.

# 2. Set the PAT as an env var for the API calls (and for the git remote URL)
export GITHUB_TOKEN="<their_pat>"

# 3. CRITICAL: override git identity for THIS repo, not globally.
#    Do NOT touch their global git config — they're probably set up
#    for their own projects.
cd /path/to/their/project
git config user.name "Their Name"
git config user.email "their-github-email@example.com"

# 4. Create the remote repo AS THEM via the API (this is what the PAT controls)
curl -s -X POST \
  -H "Authorization: token $GITHUB_TOKEN" \
  https://api.github.com/user/repos \
  -d '{"name":"their-new-repo","private":false,"auto_init":false}'

# 5. Push. The remote authenticates with their PAT; the commits are
#    attributed to their name+email because of step 3.
git remote add origin https://github.com/<their_username>/their-new-repo.git
git push -u origin main

# 6. (Recommended) revoke the PAT when done. The human can do this
#    themselves at https://github.com/settings/tokens
unset GITHUB_TOKEN
```

### When you also have `gh` but it's the wrong user

If your `gh` CLI is already authenticated as yourself and you don't want to log it out (because you have other work), use `GH_TOKEN` to override per-command without changing gh's global session:

```bash
# Verify the wrong-user problem first
gh api user --jq .login
# Output: your-agent-username   <-- WRONG

# Use a per-command PAT override without changing gh's global session
GH_TOKEN=*** gh api user --jq .login
# Output: their-username        <-- correct

# Create a repo as them
GH_TOKEN=*** gh repo create their-new-repo --public --source . --push
```

`GH_TOKEN` overrides `gh`'s stored auth for that single command. No global `gh auth logout` needed. Your agent stays logged in as itself, the human's work gets attributed to them.

### Pitfalls

- **Don't reuse the human's PAT for anything else.** The moment you have it, minimize its surface: use `GH_TOKEN`/`GITHUB_TOKEN` only, never paste it into a remote URL that gets logged, and unset it after the task.
- **Don't set `user.email` to a no-reply address unless that's their actual GitHub email.** If their GitHub account uses the private `12345+username@users.noreply.github.com` and you set the real email, the commits won't link to their profile and the contribution graph stays empty.
- **Don't push in `--mirror` mode without confirming.** A mirror push of a repo with existing history from a different identity creates a confusing history. Default to a clean new remote.
- **Tag the human when you're done** so they can verify attribution and revoke the token.
