GitHub API Skill
Part of Cardo — "Spawn many. Swarm the objective."
Use the GitHub API via curl for all GitHub operations. This skill provides standardized access to issues, PRs, reviews, and CI status.
Prerequisites
bash
# Token must be set in environment export GITHUB_API_TOKEN=ghp_...
Configuration
bash
REPO_OWNER="{{GITHUB_OWNER}}"
REPO_NAME="{{GITHUB_REPO}}"
BASE_URL="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}"
Helper Function
Use this wrapper for all API calls:
bash
gh_api() {
local method="${1:-GET}"
local endpoint="$2"
local data="$3"
if [ -n "$data" ]; then
curl -s -X "$method" \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-d "$data" \
"${BASE_URL}${endpoint}"
else
curl -s -X "$method" \
-H "Authorization: token $GITHUB_TOKEN" \
"${BASE_URL}${endpoint}"
fi
}
Issues
Read Issue
bash
gh_api GET "/issues/{number}"
# Example
gh_api GET "/issues/13"
Get Issue Comments
bash
gh_api GET "/issues/{number}/comments"
Post Issue Comment
bash
gh_api POST "/issues/{number}/comments" '{"body":"Comment text"}'
# With multiline/markdown
gh_api POST "/issues/13/comments" '{
"body": "## Status Update\n\n- Phase: Implementation\n- Progress: 50%"
}'
Update Issue
bash
# Update labels
gh_api PATCH "/issues/{number}" '{"labels":["in-progress","cardo"]}'
# Update state
gh_api PATCH "/issues/{number}" '{"state":"closed"}'
# Update assignees
gh_api PATCH "/issues/{number}" '{"assignees":["username"]}'
# Multiple updates
gh_api PATCH "/issues/13" '{
"labels": ["in-progress", "cardo"],
"assignees": ["username"]
}'
Create Issue
bash
gh_api POST "/issues" '{
"title": "Child task: Implement auth",
"body": "Parent: #13\n\n## Description\n...",
"labels": ["agent-ready", "cardo"]
}'
Close Issue
bash
gh_api PATCH "/issues/{number}" '{"state":"closed"}'
Add Labels
bash
gh_api POST "/issues/{number}/labels" '{"labels":["blocked","needs-review"]}'
Remove Label
bash
curl -s -X DELETE \
-H "Authorization: token $GITHUB_TOKEN" \
"${BASE_URL}/issues/{number}/labels/{label_name}"
Pull Requests
Create PR
bash
gh_api POST "/pulls" '{
"title": "feat: implement subscribe service",
"body": "Closes #13\n\n## Changes\n- Added subscription API\n- Unit tests\n\n## Screenshots\n...",
"head": "feature/13-subscribe-service",
"base": "main"
}'
Get PR
bash
gh_api GET "/pulls/{number}"
List PR Files
bash
gh_api GET "/pulls/{number}/files"
Update PR
bash
gh_api PATCH "/pulls/{number}" '{
"title": "Updated title",
"body": "Updated description"
}'
Merge PR
bash
# Squash merge (recommended)
gh_api PUT "/pulls/{number}/merge" '{"merge_method":"squash"}'
# Regular merge
gh_api PUT "/pulls/{number}/merge" '{"merge_method":"merge"}'
# Rebase merge
gh_api PUT "/pulls/{number}/merge" '{"merge_method":"rebase"}'
# With custom commit message
gh_api PUT "/pulls/15/merge" '{
"merge_method": "squash",
"commit_title": "feat: implement subscribe service (#15)",
"commit_message": "Closes #13"
}'
Close PR (without merging)
bash
gh_api PATCH "/pulls/{number}" '{"state":"closed"}'
PR Reviews
Get Reviews
bash
gh_api GET "/pulls/{number}/reviews"
Get Review Comments (line-level)
bash
gh_api GET "/pulls/{number}/comments"
Reply to Review Comment
bash
gh_api POST "/pulls/{number}/comments/{comment_id}/replies" '{"body":"Reply text"}'
Post General PR Comment
bash
# Uses issues endpoint (PRs are issues)
gh_api POST "/issues/{number}/comments" '{"body":"General comment"}'
Request Reviewers
bash
gh_api POST "/pulls/{number}/requested_reviewers" '{"reviewers":["username"]}'
Remove Reviewer Request
bash
curl -s -X DELETE \
-H "Authorization: token $GITHUB_TOKEN" \
-d '{"reviewers":["username"]}' \
"${BASE_URL}/pulls/{number}/requested_reviewers"
CI / Checks
Get Check Runs for Commit
bash
gh_api GET "/commits/{sha}/check-runs"
# Parse status
gh_api GET "/commits/abc123/check-runs" | jq '.check_runs[] | {name, status, conclusion}'
Get Combined Status
bash
gh_api GET "/commits/{sha}/status"
Get Workflow Runs
bash
gh_api GET "/actions/runs" # Filter by branch gh_api GET "/actions/runs?branch=feature/13-subscribe-service" # Latest run gh_api GET "/actions/runs?per_page=1"
Get Workflow Run Details
bash
gh_api GET "/actions/runs/{run_id}"
Get Workflow Run Jobs
bash
gh_api GET "/actions/runs/{run_id}/jobs"
Re-run Failed Workflow
bash
gh_api POST "/actions/runs/{run_id}/rerun"
Branches
List Branches
bash
gh_api GET "/branches"
Get Branch
bash
gh_api GET "/branches/{branch}"
# Get latest commit SHA
gh_api GET "/branches/main" | jq -r '.commit.sha'
Delete Branch
bash
curl -s -X DELETE \
-H "Authorization: token $GITHUB_TOKEN" \
"${BASE_URL}/git/refs/heads/{branch}"
Commits
List Commits
bash
gh_api GET "/commits" # On specific branch gh_api GET "/commits?sha=feature/13-subscribe-service" # With pagination gh_api GET "/commits?per_page=10&page=1"
Get Commit
bash
gh_api GET "/commits/{sha}"
Compare Commits
bash
gh_api GET "/compare/{base}...{head}"
# Example: compare main to feature branch
gh_api GET "/compare/main...feature/13-subscribe-service"
Repository
Get Repo Info
bash
gh_api GET ""
Get README
bash
gh_api GET "/readme"
Get File Contents
bash
gh_api GET "/contents/{path}"
# Example
gh_api GET "/contents/package.json"
# Decode content (base64)
gh_api GET "/contents/package.json" | jq -r '.content' | base64 -d
Error Handling
Check for Errors
bash
response=$(gh_api GET "/issues/999") if echo "$response" | jq -e '.message' > /dev/null 2>&1; then echo "Error: $(echo "$response" | jq -r '.message')" exit 1 fi
Common Error Responses
| Status | Meaning | Action |
|---|---|---|
| 401 | Bad token | Check GITHUB_TOKEN |
| 403 | Rate limited or forbidden | Wait or check permissions |
| 404 | Not found | Check resource exists |
| 422 | Validation failed | Check request body |
Rate Limit Check
bash
curl -s -H "Authorization: token $GITHUB_TOKEN" \ https://api.github.com/rate_limit | jq '.rate'
Pagination
GitHub API returns max 100 items per page. For lists:
bash
# First page
gh_api GET "/issues?per_page=100&page=1"
# Check for more pages in response headers
curl -s -I -H "Authorization: token $GITHUB_TOKEN" \
"${BASE_URL}/issues?per_page=100" | grep -i "link:"
Cardo-Specific Patterns
Post Orchestration Log Update
bash
gh_api POST "/issues/13/comments" '{
"body": "## Cardo Update\n\n| Phase | Status |\n|-------|--------|\n| Intake | Done |\n| Discovery | In Progress |\n\n**Current:** Clarifying requirements with Product Owner"
}'
Mark Issue as In-Progress
bash
gh_api PATCH "/issues/13" '{
"labels": ["in-progress", "cardo"],
"assignees": ["username"]
}'
Mark Issue as Blocked
bash
gh_api PATCH "/issues/13" '{
"labels": ["blocked", "cardo"]
}'
gh_api POST "/issues/13/comments" '{
"body": "**Blocked**\n\nPhase: Implementation\nAttempts: 3/3\nError: Test failure in auth module\n\n_Escalating to team lead._"
}'
Create PR with Full Context
bash
gh_api POST "/pulls" '{
"title": "feat(subscribe): implement subscribe service",
"body": "## Summary\nImplements subscription service.\n\nCloses #13\n\n## Changes\n- Added SubscribeService class\n- Unit tests with 95% coverage\n- E2E tests\n\n## Checklist\n- [x] Tests passing\n- [x] E2E passing\n- [x] Internal review complete",
"head": "feature/13-subscribe-service",
"base": "main"
}'
Check if PR Ready to Merge
bash
pr_number=15 sha=$(gh_api GET "/pulls/$pr_number" | jq -r '.head.sha') # Check CI status ci_status=$(gh_api GET "/commits/$sha/status" | jq -r '.state') # Check reviews reviews=$(gh_api GET "/pulls/$pr_number/reviews" | jq '[.[] | select(.state == "APPROVED")] | length') if [ "$ci_status" = "success" ] && [ "$reviews" -gt 0 ]; then echo "PR ready to merge" else echo "CI: $ci_status, Approvals: $reviews" fi
Branch Protection
Get Current Protection
bash
# Via gh CLI (preferred)
gh api repos/{owner}/{repo}/branches/main/protection
# Parse specific settings
gh api repos/{owner}/{repo}/branches/main/protection \
--jq '{
required_status_checks: .required_status_checks,
required_reviews: .required_pull_request_reviews,
enforce_admins: .enforce_admins.enabled
}'
Set Branch Protection
bash
# Minimal protection for small team (1-2 devs)
gh api repos/{owner}/{repo}/branches/main/protection \
-X PUT \
-H "Accept: application/vnd.github+json" \
-f required_status_checks='{"strict":true,"contexts":["lint","test"]}' \
-F enforce_admins=true \
-f required_pull_request_reviews='{"required_approving_review_count":0,"dismiss_stale_reviews":true}' \
-f restrictions=null \
-F required_linear_history=true \
-F allow_force_pushes=false \
-F allow_deletions=false
Full Protection JSON (for complex setups)
bash
gh api repos/{owner}/{repo}/branches/main/protection \
-X PUT \
--input - <<'EOF'
{
"required_status_checks": {
"strict": true,
"contexts": ["lint", "test", "typecheck"]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"required_approving_review_count": 1,
"dismiss_stale_reviews": true,
"require_code_owner_reviews": false,
"require_last_push_approval": false
},
"restrictions": null,
"required_linear_history": true,
"allow_force_pushes": false,
"allow_deletions": false,
"required_signatures": false
}
EOF
Protection Settings Reference
| Setting | Type | Description |
|---|---|---|
required_status_checks.strict | boolean | Require branch to be up-to-date with base before merging |
required_status_checks.contexts | string[] | CI job names that must pass (match jobs.<id>.name in workflow) |
enforce_admins | boolean | Apply rules to admins too |
required_pull_request_reviews.required_approving_review_count | 0-6 | Number of approvals needed (0 = no review required, PR still required) |
required_pull_request_reviews.dismiss_stale_reviews | boolean | Dismiss approvals when new commits pushed |
required_linear_history | boolean | Require squash or rebase (no merge commits) |
allow_force_pushes | boolean | Allow force push to protected branch |
allow_deletions | boolean | Allow branch deletion |
required_signatures | boolean | Require signed commits |
restrictions | object|null | Limit who can push (null = no restriction beyond PR rules) |
Remove Branch Protection
bash
# Remove all protection (requires admin)
gh api repos/{owner}/{repo}/branches/main/protection -X DELETE
Check if Branch is Protected
bash
gh api repos/{owner}/{repo}/branches/main --jq '.protected'
Note: Repository Rulesets (Modern Alternative)
GitHub also offers Repository Rulesets — a newer, more flexible system that can replace or complement branch protection rules. Key differences:
| Factor | Branch Protection | Rulesets |
|---|---|---|
| Scope | One rule per branch | Multiple branches/tags via patterns |
| Stacking | One rule per branch | Multiple rulesets aggregate (most restrictive wins) |
| Toggle | Must delete and recreate | Toggle enforcement on/off |
| Extra rules | No | Commit message patterns, file restrictions, code scanning |
For basic needs (PR gate + CI checks), classic branch protection is simpler. Consider rulesets for future expansion.
bash
# List rulesets
gh api repos/{owner}/{repo}/rulesets --jq '.[] | {id, name, enforcement}'
# Get effective rules on a branch
gh api repos/{owner}/{repo}/rules/branches/main
GitHub Secrets
List Secrets
bash
# Repository secrets gh secret list # Environment secrets gh secret list --env production
Set a Secret
bash
# From value gh secret set MY_TOKEN --body "your-token-value" # From file gh secret set SERVICE_ACCOUNT_KEY < service-account.json # From stdin (pipe) echo "token-value" | gh secret set MY_TOKEN # Environment-scoped gh secret set DEPLOY_KEY --env production --body "value" # Batch from .env file (loads all KEY=value pairs) gh secret set -f .env # Dependabot secrets (separate from Actions secrets) gh secret set REGISTRY_TOKEN --app dependabot --body "value"
Delete a Secret
bash
gh secret delete MY_TOKEN gh secret delete DEPLOY_KEY --env production
Variables (Non-Secret Config)
bash
# Repository variables (visible in logs, not encrypted) gh variable set NODE_ENV --body "production" gh variable list # Environment-scoped variables gh variable set API_URL --env production --body "https://api.example.com" gh variable list --env production gh variable delete API_URL --env production
Secrets via API (for automation)
bash
# Step 1: Get the repository public key (needed for encryption)
gh api repos/{owner}/{repo}/actions/secrets/public-key \
--jq '{key_id, key}'
# Step 2: Encrypt the secret value with the public key
# (Requires libsodium — complex. Prefer `gh secret set` instead.)
# List secrets via API
gh api repos/{owner}/{repo}/actions/secrets --jq '.secrets[].name'
GitHub Environments
Create Environment
bash
gh api repos/{owner}/{repo}/environments/production \
-X PUT \
--input - <<'EOF'
{
"wait_timer": 0,
"reviewers": [],
"deployment_branch_policy": {
"protected_branches": true,
"custom_branch_policies": false
}
}
EOF
Environment with Required Reviewers
bash
gh api repos/{owner}/{repo}/environments/production \
-X PUT \
--input - <<'EOF'
{
"reviewers": [
{
"type": "User",
"id": USER_ID
}
],
"deployment_branch_policy": {
"protected_branches": true,
"custom_branch_policies": false
}
}
EOF
List Environments
bash
gh api repos/{owner}/{repo}/environments --jq '.environments[].name'
Use Environment in Workflow
yaml
jobs:
deploy:
environment: production # Triggers environment protection rules
runs-on: ubuntu-latest
steps:
- run: echo "Deploying with ${{ secrets.DEPLOY_KEY }}"
GitHub Actions API
List Workflow Runs
bash
# All runs gh run list --limit 10 # Filter by workflow gh run list --workflow pr-gate.yml --limit 5 # Filter by branch gh run list --branch feature/155-branch-protection --limit 5 # Filter by status gh run list --status failure --limit 5
View Run Details
bash
# Summary
gh run view {run-id}
# Full logs
gh run view {run-id} --log
# Failed jobs only
gh run view {run-id} --log-failed
Re-Run
bash
# Re-run all jobs
gh run rerun {run-id}
# Re-run only failed jobs
gh run rerun {run-id} --failed
Cancel a Run
bash
gh run cancel {run-id}
Manage Workflows
bash
# List workflows gh workflow list # View workflow details gh workflow view pr-gate.yml # Disable/enable a workflow gh workflow disable pr-gate.yml gh workflow enable pr-gate.yml # Trigger a workflow_dispatch gh workflow run e2e.yml -f run_unit_tests=true -f run_e2e=true
Milestones
List Milestones
bash
gh api repos/{owner}/{repo}/milestones --jq '.[] | {number, title, state, open_issues, closed_issues}'
Create Milestone
bash
gh api repos/{owner}/{repo}/milestones \
-X POST \
-f title="Sprint 2 (Feb 13 -- Feb 15)" \
-f description="Sprint 2 goal" \
-f due_on="2026-02-15T23:59:59Z" \
-f state="open"
Close Milestone
bash
gh api repos/{owner}/{repo}/milestones/{number} \
-X PATCH \
-f state="closed"
Security Notes
- •Never hardcode
GITHUB_TOKENin files - •Use fine-grained PAT with minimal scopes
- •Token visible in cloud session logs — rotate periodically
- •For production, consider GitHub App instead of PAT
- •Use environment secrets for production-only credentials
- •Never echo secrets in CI logs — they're masked but don't test limits