"GitHub end-to-end workflow: auth, repos, issues, code review, PRs, CI, releases. Use the `gh` CLI when available, fall back to `git` + `curl` for headless / no-sudo machines."
Resources
3Install
npx skillscat add russ534/myopenclawbot/github-workflow Install via the SkillsCat registry.
GitHub Workflow
End-to-end guide for working with GitHub from the agent — covers the full lifecycle from authentication through merge, plus repo setup, issue triage, code review, and release management. Every section shows the gh CLI way first, then a git + curl fallback for machines without gh (no sudo, headless servers, minimal containers).
Sections:
- Authentication & Environment — start here
- Repository Management — clone, create, fork, configure, secrets, releases, gists, Actions
- Issues Management — view, create, label, assign, triage
- Code Review — local pre-push review, PR review with inline comments
- Pull Request Workflow — branch, commit, push, open PR, monitor CI, merge
0. Authentication & Environment
Every GitHub workflow starts with the same auth detection. Get this right once, then every section below "just works."
Detection Flow
# Check what's available
git --version
gh --version 2>/dev/null || echo "gh not installed"
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:
- If
gh auth statusshows authenticated → useghfor everything - If
ghis installed but not authenticated → usegh auth login(Method 2 below) - If
ghis not installed → use git-only Method 1 below (no sudo needed)
Method 1: Git-Only Authentication (No gh, No sudo)
Works on any machine with git installed. No root access needed.
Option A: HTTPS with Personal Access Token (recommended)
Tell the user to go to https://github.com/settings/tokens
- Click "Generate new token (classic)"
- Name it (e.g. "hermes-agent")
- Select scopes:
repo,workflow,read:org(if working with org repos) - Set expiration (90 days is a good default)
- Copy the token — it won't be shown again
Configure git to store the token:
# "store" saves to ~/.git-credentials in plaintext (simple, persistent) git config --global credential.helper store # Trigger auth — git prompts for credentials # Username: <github-username> # Password: <paste the personal access token, NOT their password> git ls-remote https://github.com/<username>/<any-repo>.gitAfter entering once, they're saved and reused forever. Alternatives:
# In-memory only, expires after 8 hours git config --global credential.helper 'cache --timeout=28800' # Embed token in the remote URL (per-repo, no prompts) git remote set-url origin https://<username>:<token>@github.com/<owner>/<repo>.gitConfigure git identity (required for commits):
git config --global user.name "Their Name" git config --global user.email "their-email@example.com"Verify:
git config --global user.name git config --global user.email
Option B: SSH Key Authentication
# Check for existing keys
ls -la ~/.ssh/id_*.pub 2>/dev/null || echo "No SSH keys found"
# Generate ed25519 key if needed
ssh-keygen -t ed25519 -C "their-email@example.com" -f ~/.ssh/id_ed25519 -N ""
cat ~/.ssh/id_ed25519.pub # user pastes this at https://github.com/settings/keys
# Test
ssh -T git@github.com # "Hi <username>! You've successfully authenticated..."
# Auto-rewrite HTTPS → SSH for GitHub
git config --global url."git@github.com:".insteadOf "https://github.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)
gh auth login
# Token-based login (headless / SSH servers)
echo "<THEIR_TOKEN>" | gh auth login --with-token
gh auth setup-git
# Verify
gh auth statusReusable Auth Detection Block
Drop this at the top of any GitHub script — it sets AUTH (gh or git), GITHUB_TOKEN, GH_USER, GH_OWNER, GH_REPO, GH_OWNER_REPO.
# Source the helper script (preferred):
source "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-workflow/scripts/gh-env.sh"
# Sets: GH_AUTH_METHOD, GITHUB_TOKEN, GH_USER, GH_OWNER, GH_REPO, GH_OWNER_REPO
# Or inline:
if command -v gh &>/dev/null && gh auth status &>/dev/null; then
AUTH="gh"
else
AUTH="git"
if [ -z "$GITHUB_TOKEN" ]; then
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
fi
fi
fi
REMOTE_URL=$(git remote get-url origin 2>/dev/null)
if [ -n "$REMOTE_URL" ] && echo "$REMOTE_URL" | grep -q "github.com"; then
OWNER_REPO=$(echo "$REMOTE_URL" | sed -E 's|.*github\.com[:/]||; s|\.git$||')
OWNER=$(echo "$OWNER_REPO" | cut -d/ -f1)
REPO=$(echo "$OWNER_REPO" | cut -d/ -f2)
fiAuth 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 stale — run git credential reject then re-authenticate |
ssh: connect to host github.com port 22: Connection refused |
Use SSH over HTTPS port: add Host github.com / Port 443 / Hostname ssh.github.com to ~/.ssh/config |
| Credentials not persisting | Check git config --global credential.helper — must be store or cache |
| Multiple GitHub accounts | SSH with different keys per host alias, or per-repo credential URLs |
gh: command not found + no sudo |
Use git-only Method 1 above — no installation needed |
1. Repository Management
Create, clone, fork, configure, and manage GitHub repositories.
1.1 Cloning
Cloning is pure git — works identically either way:
# HTTPS (works with credential helper or token-embedded URL)
git clone https://github.com/owner/repo-name.git
# Into a specific directory
git clone https://github.com/owner/repo-name.git ./my-local-dir
# Shallow (faster for large repos)
git clone --depth 1 https://github.com/owner/repo-name.git
# Specific branch
git clone --branch develop https://github.com/owner/repo-name.git
# SSH (if configured)
git clone git@github.com:owner/repo-name.gitWith gh shorthand:
gh repo clone owner/repo-name
gh repo clone owner/repo-name -- --depth 11.2 Creating Repositories
With gh:
# Public, cloned
gh repo create my-new-project --public --clone
# Private, with description and license
gh repo create my-new-project --private --description "A useful tool" --license MIT --clone
# Under an organization
gh repo create my-org/my-new-project --public --clone
# From existing local directory
cd /path/to/existing/project
gh repo create my-project --source . --public --pushWith git + curl:
# Create the remote repo via API
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/user/repos \
-d '{
"name": "my-new-project",
"description": "A useful tool",
"private": false,
"auto_init": true,
"license_template": "mit"
}'
# Clone it
git clone https://github.com/$GH_USER/my-new-project.git
cd my-new-project
# -- OR -- push an existing local directory to the new repo
cd /path/to/existing/project
git init && git add . && git commit -m "Initial commit"
git remote add origin https://github.com/$GH_USER/my-new-project.git
git push -u origin mainUnder an organization:
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/orgs/my-org/repos \
-d '{"name": "my-new-project", "private": false}'From a template:
# gh
gh repo create my-new-app --template owner/template-repo --public --clone
# curl
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/owner/template-repo/generate \
-d '{"owner": "'"$GH_USER"'", "name": "my-new-app", "private": false}'1.3 Forking
# gh — does the API call, clone, and remote setup in one step
gh repo fork owner/repo-name --clone
# curl + manual
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/owner/repo-name/forks
sleep 3
git clone https://github.com/$GH_USER/repo-name.git
cd repo-name
git remote add upstream https://github.com/owner/repo-name.gitKeep a fork in sync:
git fetch upstream
git checkout main
git merge upstream/main
git push origin main
# gh shortcut
gh repo sync $GH_USER/repo-name1.4 Repository Information
# gh
gh repo view owner/repo-name
gh repo list --limit 20
gh search repos "machine learning" --language python --sort stars
# curl
curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO
curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/user/repos?per_page=20&sort=updated"1.5 Repository Settings
# gh
gh repo edit --description "Updated description" --visibility public
gh repo edit --enable-wiki=false --enable-issues=true
gh repo edit --default-branch main
gh repo edit --add-topic "machine-learning,python"
gh repo edit --enable-auto-merge
# curl
curl -s -X PATCH \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO \
-d '{"description": "Updated description", "has_wiki": false, "has_issues": true, "allow_auto_merge": true}'
# Update topics
curl -s -X PUT \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.mercy-preview+json" \
https://api.github.com/repos/$OWNER/$REPO/topics \
-d '{"names": ["machine-learning", "python", "automation"]}'1.6 Branch Protection
# View current protection
curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/branches/main/protection
# Set protection
curl -s -X PUT \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/branches/main/protection \
-d '{
"required_status_checks": {"strict": true, "contexts": ["ci/test", "ci/lint"]},
"enforce_admins": false,
"required_pull_request_reviews": {"required_approving_review_count": 1},
"restrictions": null
}'1.7 Secrets Management (GitHub Actions)
# gh — much simpler
gh secret set API_KEY --body "your-secret-value"
gh secret set SSH_KEY < ~/.ssh/id_rsa
gh secret list
gh secret delete API_KEYWith curl, secrets need encryption with the repo's public key (requires PyNaCl):
KEY_ID=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/secrets/public-key \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['key_id'])")
PUBKEY=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/secrets/public-key \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['key'])")
ENCRYPTED=$(python3 -c "
from base64 import b64encode
from nacl import encoding, public
sealed = public.SealedBox(public.PublicKey('$PUBKEY'.encode(), encoding.Base64Encoder)).encrypt(b'your-secret-value')
print(b64encode(sealed).decode())")
curl -s -X PUT \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/secrets/API_KEY \
-d "{\"encrypted_value\": \"$ENCRYPTED\", \"key_id\": \"$KEY_ID\"}"For secrets,
gh secret setis dramatically simpler. If setting secrets is needed andghisn't available, recommend installing it for just that operation.
1.8 Releases
# gh
gh release create v1.0.0 --title "v1.0.0" --generate-notes
gh release create v2.0.0-rc1 --draft --prerelease --generate-notes
gh release create v1.0.0 ./dist/binary --title "v1.0.0" --notes "Release notes"
gh release list
gh release download v1.0.0 --dir ./downloads
# curl
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/releases \
-d '{"tag_name": "v1.0.0", "name": "v1.0.0", "body": "## Changelog\n- Feature A", "generate_release_notes": true}'
# Upload a release asset
RELEASE_ID=<id_from_create_response>
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/octet-stream" \
"https://uploads.github.com/repos/$OWNER/$REPO/releases/$RELEASE_ID/assets?name=binary-amd64" \
--data-binary @./dist/binary-amd641.9 GitHub Actions Workflows
# gh
gh workflow list
gh run list --limit 10
gh run view <RUN_ID>
gh run view <RUN_ID> --log-failed
gh run rerun <RUN_ID>
gh run rerun <RUN_ID> --failed
gh workflow run ci.yml --ref main
gh workflow run deploy.yml -f environment=staging
# curl
curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/workflows
curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/actions/runs?per_page=10"
# Download failed run logs
RUN_ID=<run_id>
curl -sL -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/logs \
-o /tmp/ci-logs.zip
cd /tmp && unzip -o ci-logs.zip -d ci-logs
# Rerun
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/rerun
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/rerun-failed-jobs
# Manually trigger
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/dispatches \
-d '{"ref": "main", "inputs": {"environment": "staging"}}'1.10 Gists
# gh
gh gist create script.py --public --desc "Useful script"
gh gist list
# curl
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/gists \
-d '{"description": "Useful script", "public": true, "files": {"script.py": {"content": "print(\"hello\")"}}}'1.11 Repo Quick Reference
| Action | gh | git + curl |
|---|---|---|
| Clone | gh repo clone o/r |
git clone https://github.com/o/r.git |
| Create repo | gh repo create name --public |
curl POST /user/repos |
| Fork | gh repo fork o/r --clone |
curl POST /repos/o/r/forks + git clone |
| Repo info | gh repo view o/r |
curl GET /repos/o/r |
| Edit settings | gh repo edit --... |
curl PATCH /repos/o/r |
| Create release | gh release create v1.0 |
curl POST /repos/o/r/releases |
| List workflows | gh workflow list |
curl GET /repos/o/r/actions/workflows |
| Rerun CI | gh run rerun ID |
curl POST /repos/o/r/actions/runs/ID/rerun |
| Set secret | gh secret set KEY |
curl PUT /repos/o/r/actions/secrets/KEY (+ encryption) |
2. Issues Management
Create, search, triage, and manage GitHub issues. Every command shows gh first, then curl.
2.1 Viewing Issues
# gh
gh issue list
gh issue list --state open --label "bug"
gh issue list --assignee @me
gh issue list --search "authentication error" --state all
gh issue view 42
# curl
curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues?state=open&per_page=20"
curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues?state=open&labels=bug&per_page=20"
curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/issues/42
curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/search/issues?q=authentication+error+repo:$OWNER/$REPO"GitHub's
/issuesendpoint returns PRs too — filter withif 'pull_request' not in i:when iterating.
2.2 Creating Issues
# gh
gh issue create \
--title "Login redirect ignores ?next= parameter" \
--body "## Description
After logging in, users always land on /dashboard.
## Steps to Reproduce
1. Navigate to /settings while logged out
2. Get redirected to /login?next=/settings
3. Log in
4. Actual: redirected to /dashboard (should go to /settings)
## Expected Behavior
Respect the ?next= query parameter." \
--label "bug,backend" \
--assignee "username"
# curl
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/issues \
-d '{
"title": "Login redirect ignores ?next= parameter",
"body": "## Description\nAfter logging in, users always land on /dashboard.",
"labels": ["bug", "backend"],
"assignees": ["username"]
}'Use the templates/bug-report.md and templates/feature-request.md files in this skill for full structured issue templates.
2.3 Managing Issues
# Labels
gh issue edit 42 --add-label "priority:high,bug"
gh issue edit 42 --remove-label "needs-triage"
# Assignment
gh issue edit 42 --add-assignee username
gh issue edit 42 --add-assignee @me
# Commenting
gh issue comment 42 --body "Investigated — root cause is in auth middleware."
# Closing and reopening
gh issue close 42
gh issue close 42 --reason "not planned"
gh issue reopen 42curl equivalents: POST to /issues/N/labels, /issues/N/assignees, /issues/N/comments; PATCH /issues/N with state: closed / open.
2.4 Linking Issues to PRs
Issues close automatically when a PR merges with the right keywords in the body:
Closes #42
Fixes #42
Resolves #42To create a branch from an issue:
gh issue develop 42 --checkout
# Manual equivalent
git checkout main && git pull origin main
git checkout -b fix/issue-42-login-redirect2.5 Triage Workflow
When asked to triage issues:
- List untriaged:
gh issue list --label "needs-triage" --state open - Read and categorize each issue (view details, understand the bug/feature)
- Apply labels and priority (Section 2.3)
- Assign if the owner is clear
- Comment with triage notes if needed
2.6 Bulk Operations
# Close all issues with a specific label
gh issue list --label "wontfix" --json number --jq '.[].number' | \
xargs -I {} gh issue close {} --reason "not planned"2.7 Issues Quick Reference
| Action | gh | curl endpoint |
|---|---|---|
| List issues | gh issue list |
GET /repos/{o}/{r}/issues |
| View issue | gh issue view N |
GET /repos/{o}/{r}/issues/N |
| Create issue | gh issue create ... |
POST /repos/{o}/{r}/issues |
| Add labels | gh issue edit N --add-label ... |
POST /repos/{o}/{r}/issues/N/labels |
| Assign | gh issue edit N --add-assignee ... |
POST /repos/{o}/{r}/issues/N/assignees |
| Comment | gh issue comment N --body ... |
POST /repos/{o}/{r}/issues/N/comments |
| Close | gh issue close N |
PATCH /repos/{o}/{r}/issues/N |
| Search | gh issue list --search "..." |
GET /search/issues?q=... |
3. Code Review
Perform code reviews on local changes before pushing, or review open PRs on GitHub. Most of this section uses plain git — the gh/curl split only matters for PR-level interactions.
3.1 Reviewing Local Changes (Pre-Push)
Pure git — works everywhere, no API needed.
# Staged changes (what would be committed)
git diff --staged
# All changes vs main (what a PR would contain)
git diff main...HEAD
# File names only
git diff main...HEAD --name-only
# Stat summary
git diff main...HEAD --statReview strategy:
Big picture first:
git diff main...HEAD --stat git log main..HEAD --onelineReview file by file — use
read_fileon changed files for full context, plus the diff:git diff main...HEAD -- src/auth/login.pyCheck for common issues:
# Debug statements, TODOs, console.logs left behind git diff main...HEAD | grep -n "print(\|console\.log\|TODO\|FIXME\|HACK\|XXX\|debugger" # Large files accidentally staged git diff main...HEAD --stat | sort -t'|' -k2 -rn | head -10 # Secrets or credential patterns git diff main...HEAD | grep -in "password\|secret\|api_key\|token.*=\|private_key" # Merge conflict markers git diff main...HEAD | grep -n "<<<<<<\|>>>>>>\|======="Present structured feedback to the user using the format in
references/review-output-template.md.
3.2 Reviewing a Pull Request on GitHub
# gh
gh pr view 123
gh pr diff 123
gh pr diff 123 --name-only
# curl
curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER
curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER/filesCheck out the PR locally for full review (works with plain git):
git fetch origin pull/123/head:pr-123
git checkout pr-123
# Now read_file, search_files, run tests, etc. are all available
git diff main...pr-123
# gh shortcut
gh pr checkout 1233.3 Leaving Comments
General PR comment:
# gh
gh pr comment 123 --body "Overall looks good, a few suggestions below."
# curl — note: PRs and issues share the comments endpoint
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/issues/$PR_NUMBER/comments \
-d '{"body": "Overall looks good, a few suggestions below."}'Single inline comment:
# gh
HEAD_SHA=$(gh pr view 123 --json headRefOid --jq '.headRefOid')
gh api repos/$OWNER/$REPO/pulls/123/comments --method POST \
-f body="This could be simplified with a list comprehension." \
-f path="src/auth/login.py" \
-f commit_id="$HEAD_SHA" \
-f line=45 \
-f side="RIGHT"
# curl
HEAD_SHA=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER \
| python3 -c "import sys,json; print(json.load(sys.stdin)['head']['sha'])")
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER/comments \
-d "{\"body\": \"This could be simplified with a list comprehension.\", \"path\": \"src/auth/login.py\", \"commit_id\": \"$HEAD_SHA\", \"line\": 45, \"side\": \"RIGHT\"}"3.4 Submitting a Formal Review
# gh
gh pr review 123 --approve --body "LGTM!"
gh pr review 123 --request-changes --body "See inline comments."
gh pr review 123 --comment --body "Some suggestions, nothing blocking."
# curl — multi-comment review submitted atomically
HEAD_SHA=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER \
| python3 -c "import sys,json; print(json.load(sys.stdin)['head']['sha'])")
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews \
-d "{
\"commit_id\": \"$HEAD_SHA\",
\"event\": \"COMMENT\",
\"body\": \"Code review from Hermes Agent\",
\"comments\": [
{\"path\": \"src/auth.py\", \"line\": 45, \"body\": \"Use parameterized queries to prevent SQL injection.\"},
{\"path\": \"src/models/user.py\", \"line\": 23, \"body\": \"Hash passwords with bcrypt before storing.\"},
{\"path\": \"tests/test_auth.py\", \"line\": 1, \"body\": \"Add test for expired token edge case.\"}
]
}"Event values: "APPROVE", "REQUEST_CHANGES", "COMMENT". The line field is the line number in the new version. For deleted lines, use "side": "LEFT".
3.5 Review Checklist
When performing a code review (local or PR), systematically check:
Correctness
- Does the code do what it claims?
- Edge cases handled (empty inputs, nulls, large data, concurrent access)?
- Error paths handled gracefully?
Security
- No hardcoded secrets, credentials, or API keys
- Input validation on user-facing inputs
- No SQL injection, XSS, or path traversal
- Auth/authz checks where needed
Code Quality
- Clear naming (variables, functions, classes)
- No unnecessary complexity or premature abstraction
- DRY — no duplicated logic that should be extracted
- Functions are focused (single responsibility)
Testing
- New code paths tested?
- Happy path and error cases covered?
- Tests readable and maintainable?
Performance
- No N+1 queries or unnecessary loops
- Appropriate caching where beneficial
- No blocking operations in async code paths
Documentation
- Public APIs documented
- Non-obvious logic has comments explaining "why"
- README updated if behavior changed
3.6 PR Review Workflow (End-to-End)
When asked to "review PR #N", "look at this PR", or given a PR URL:
- Set up environment —
source scripts/gh-env.shor run the inline setup block - Gather PR context —
gh pr view 123,gh pr diff 123 --name-only,gh pr checks 123 - Check out the PR locally —
git fetch origin pull/$PR_NUMBER/head:pr-$PR_NUMBER && git checkout pr-$PR_NUMBER - Read the diff —
git diff main...HEAD(or per-file for large PRs); useread_filefor surrounding context - Run automated checks —
pytest,ruff check ., language-specific linter - Apply the review checklist (Section 3.5)
- Post the review to GitHub —
gh pr review $PR_NUMBER --approve/--request-changes, or atomic curl POST to/reviews - Post a summary comment — use the format from
references/review-output-template.md - Clean up —
git checkout main && git branch -D pr-$PR_NUMBER
Decision: Approve vs Request Changes vs Comment
- Approve — no critical or warning-level issues, only minor suggestions
- Request Changes — any critical or warning-level issue that should be fixed before merge
- Comment — observations and suggestions, but nothing blocking (draft PRs, uncertain findings)
4. Pull Request Workflow
Complete guide for managing the PR lifecycle — branch, commit, push, open PR, monitor CI, auto-fix, merge.
4.1 Branch Creation
This part is pure git — identical either way:
git fetch origin
git checkout main && git pull origin main
git checkout -b feat/add-user-authenticationBranch naming conventions:
feat/description— new featuresfix/description— bug fixesrefactor/description— code restructuringdocs/description— documentationci/description— CI/CD changes
4.2 Making Commits
Use the agent's file tools (write_file, patch) to make changes, then commit:
git add src/auth.py src/models/user.py tests/test_auth.py
git commit -m "feat: add JWT-based user authentication
- Add login/register endpoints
- Add User model with password hashing
- Add auth middleware for protected routes
- Add unit tests for auth flow"Conventional Commits format: type(scope): short description, body wrapped at 72 chars. Types: feat, fix, refactor, docs, test, ci, chore, perf, style, build, revert. See references/conventional-commits.md for the full reference and breaking-change conventions.
4.3 Pushing and Creating a PR
Push the branch:
git push -u origin HEADCreate the PR:
# gh
gh pr create \
--title "feat: add JWT-based user authentication" \
--body "## Summary
- Adds login and register API endpoints
- JWT token generation and validation
## Test Plan
- [ ] Unit tests pass
Closes #42"
# curl
BRANCH=$(git branch --show-current)
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/$OWNER/$REPO/pulls \
-d "{
\"title\": \"feat: add JWT-based user authentication\",
\"body\": \"## Summary\nAdds login and register API endpoints.\n\nCloses #42\",
\"head\": \"$BRANCH\",
\"base\": \"main\"
}"Options: --draft, --reviewer user1,user2, --label "enhancement", --base develop. For draft via curl, add "draft": true to the JSON. The response JSON includes the PR number — save it for later commands. Use templates/pr-body-bugfix.md and templates/pr-body-feature.md for full PR body templates.
4.4 Monitoring CI Status
# gh
gh pr checks # one-shot
gh pr checks --watch # poll every 10s
# curl — combined status
SHA=$(git rev-parse HEAD)
curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/status | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(f\"Overall: {data['state']}\")
for s in data.get('statuses', []):
print(f\" {s['context']}: {s['state']} - {s.get('description', '')}\")"
# curl — Actions check runs
curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/check-runs | python3 -c "
import sys, json
for cr in json.load(sys.stdin).get('check_runs', []):
print(f\" {cr['name']}: {cr['status']} / {cr['conclusion'] or 'pending'}\")"Poll until complete (curl):
SHA=$(git rev-parse HEAD)
for i in $(seq 1 20); do
STATUS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/status \
| python3 -c "import sys,json; print(json.load(sys.stdin)['state'])")
echo "Check $i: $STATUS"
if [ "$STATUS" = "success" ] || [ "$STATUS" = "failure" ] || [ "$STATUS" = "error" ]; then
break
fi
sleep 30
done4.5 Auto-Fixing CI Failures
When CI fails, diagnose and fix. This loop works with either auth method.
Step 1: Get failure details
# gh
gh run list --branch $(git branch --show-current) --limit 5
gh run view <RUN_ID> --log-failed
# curl
BRANCH=$(git branch --show-current)
curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/actions/runs?branch=$BRANCH&per_page=5"
RUN_ID=<run_id>
curl -sL -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/logs \
-o /tmp/ci-logs.zip
cd /tmp && unzip -o ci-logs.zip -d ci-logs && cat ci-logs/*.txtStep 2: Fix and push
# Use read_file + patch/write_file to fix
git add <fixed_files>
git commit -m "fix: resolve CI failure in <check_name>"
git pushStep 3: Verify — re-check CI status using Section 4.4.
Auto-fix loop pattern: check CI → identify failures → read logs → fix code → push → re-check. Repeat up to 3 times, then ask the user. See references/ci-troubleshooting.md for the full failure-pattern reference (test/lint/type/build/permission/timeout/docker signatures and fixes).
4.6 Merging
# gh
gh pr merge --squash --delete-branch # squash merge + delete branch
gh pr merge --auto --squash --delete-branch # auto-merge when checks pass
# curl
PR_NUMBER=<number>
curl -s -X PUT -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER/merge \
-d "{\"merge_method\": \"squash\", \"commit_title\": \"feat: add user authentication (#$PR_NUMBER)\"}"
BRANCH=$(git branch --show-current)
git push origin --delete $BRANCH
git checkout main && git pull origin main
git branch -d $BRANCHMerge methods: "merge" (merge commit), "squash", "rebase".
Enable auto-merge via GraphQL (REST doesn't support it):
PR_NODE_ID=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER \
| python3 -c "import sys,json; print(json.load(sys.stdin)['node_id'])")
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/graphql \
-d "{\"query\": \"mutation { enablePullRequestAutoMerge(input: {pullRequestId: \\\"$PR_NODE_ID\\\", mergeMethod: SQUASH}) { clientMutationId } }\"}"4.7 Complete Workflow Example
# 1. Start from clean main
git checkout main && git pull origin main
# 2. Branch
git checkout -b fix/login-redirect-bug
# 3. (Agent makes code changes with file tools)
# 4. Commit
git add src/auth/login.py tests/test_login.py
git commit -m "fix: correct redirect URL after login
Preserves the ?next= parameter instead of always redirecting to /dashboard."
# 5. Push
git push -u origin HEAD
# 6. Create PR (Section 4.3)
# 7. Monitor CI (Section 4.4)
# 8. Auto-fix if needed (Section 4.5)
# 9. Merge when green (Section 4.6)4.8 PR Quick Reference
| Action | gh | git + curl |
|---|---|---|
| List my PRs | gh pr list --author @me |
curl "https://api.github.com/repos/$OWNER/$REPO/pulls?state=open" |
| View PR diff | gh pr diff |
git diff main...HEAD (local) |
| Add comment | gh pr comment N --body "..." |
POST /issues/N/comments |
| Request review | gh pr edit N --add-reviewer user |
POST /pulls/N/requested_reviewers |
| Close PR | gh pr close N |
PATCH /pulls/N {"state":"closed"} |
| Check out someone's PR | gh pr checkout N |
git fetch origin pull/N/head:pr-N && git checkout pr-N |
Support Files
scripts/gh-env.sh— Sourceable bash helper that setsGH_AUTH_METHOD,GITHUB_TOKEN,GH_USER,GH_OWNER,GH_REPO,GH_OWNER_REPOfrom your environmentreferences/ci-troubleshooting.md— CI failure pattern signatures and fixes (test/lint/type/build/perm/timeout/docker)references/conventional-commits.md— Conventional Commits format reference with breaking-change and issue-link conventionsreferences/review-output-template.md— Standard PR review summary format (Critical/Warnings/Suggestions/Looks Good) with severity guidereferences/github-api-cheatsheet.md— REST API endpoint reference for repos, PRs, issues, reviews, comments, secrets, releasestemplates/bug-report.md— Issue body template for bug reportstemplates/feature-request.md— Issue body template for feature requeststemplates/pr-body-bugfix.md— PR body template for bug fixestemplates/pr-body-feature.md— PR body template for new features