--- name: github-issues description: Create, manage, triage, and close GitHub issues. Search existing issues, add labels, assign people, and link to PRs. Works with gh CLI or falls back to git + GitHub REST API via curl. version: 1.1.0 author: Hermes Agent license: MIT metadata: hermes: tags: [GitHub, Issues, Project-Management, Bug-Tracking, Triage] related_skills: [github-auth, github-pr-workflow] --- # GitHub Issues Management Create, search, triage, and manage GitHub issues. Each section shows `gh` first, then the `curl` fallback. ## Prerequisites - Authenticated with GitHub (see `github-auth` skill) - Inside a git repo with a GitHub remote, or specify the repo explicitly ### Setup ```bash if command -v gh &>/dev/null && gh auth status &>/dev/null; then AUTH="gh" else AUTH="git" if [ -z "$GITHUB_TOKEN" ]; then if [ -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) 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) ``` --- ## 1. Viewing Issues **With gh:** ```bash 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 ``` **With curl:** ```bash # List open issues curl -s \ -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/repos/$OWNER/$REPO/issues?state=open&per_page=20" \ | python3 -c " import sys, json for i in json.load(sys.stdin): if 'pull_request' not in i: # GitHub API returns PRs in /issues too labels = ', '.join(l['name'] for l in i['labels']) print(f\"#{i['number']:5} {i['state']:6} {labels:30} {i['title']}\")" # Filter by label curl -s \ -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/repos/$OWNER/$REPO/issues?state=open&labels=bug&per_page=20" \ | python3 -c " import sys, json for i in json.load(sys.stdin): if 'pull_request' not in i: print(f\"#{i['number']} {i['title']}\")" # View a specific issue curl -s \ -H "Authorization: token $GITHUB_TOKEN" \ https://api.github.com/repos/$OWNER/$REPO/issues/42 \ | python3 -c " import sys, json i = json.load(sys.stdin) labels = ', '.join(l['name'] for l in i['labels']) assignees = ', '.join(a['login'] for a in i['assignees']) print(f\"#{i['number']}: {i['title']}\") print(f\"State: {i['state']} Labels: {labels} Assignees: {assignees}\") print(f\"Author: {i['user']['login']} Created: {i['created_at']}\") print(f\"\n{i['body']}\")" # Search issues curl -s \ -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/search/issues?q=authentication+error+repo:$OWNER/$REPO" \ | python3 -c " import sys, json for i in json.load(sys.stdin)['items']: print(f\"#{i['number']} {i['state']:6} {i['title']}\")" ``` ## 2. Creating Issues **With gh:** ```bash 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" ``` **With curl:** ```bash 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.\n\n## Steps to Reproduce\n1. Navigate to /settings while logged out\n2. Get redirected to /login?next=/settings\n3. Log in\n4. Actual: redirected to /dashboard\n\n## Expected Behavior\nRespect the ?next= query parameter.", "labels": ["bug", "backend"], "assignees": ["username"] }' ``` ### Bug Report Template ``` ## Bug Description ## Steps to Reproduce 1. 2. ## Expected Behavior ## Actual Behavior ## Environment - OS: - Version: ``` ### Feature Request Template ``` ## Feature Description ## Motivation ## Proposed Solution ## Alternatives Considered ``` ## 3. Managing Issues ### Add/Remove Labels **With gh:** ```bash gh issue edit 42 --add-label "priority:high,bug" gh issue edit 42 --remove-label "needs-triage" ``` **With curl:** ```bash # Add labels curl -s -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ https://api.github.com/repos/$OWNER/$REPO/issues/42/labels \ -d '{"labels": ["priority:high", "bug"]}' # Remove a label curl -s -X DELETE \ -H "Authorization: token $GITHUB_TOKEN" \ https://api.github.com/repos/$OWNER/$REPO/issues/42/labels/needs-triage # List available labels in the repo curl -s \ -H "Authorization: token $GITHUB_TOKEN" \ https://api.github.com/repos/$OWNER/$REPO/labels \ | python3 -c " import sys, json for l in json.load(sys.stdin): print(f\" {l['name']:30} {l.get('description', '')}\")" ``` ### Assignment **With gh:** ```bash gh issue edit 42 --add-assignee username gh issue edit 42 --add-assignee @me ``` **With curl:** ```bash curl -s -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ https://api.github.com/repos/$OWNER/$REPO/issues/42/assignees \ -d '{"assignees": ["username"]}' ``` ### Commenting **With gh:** ```bash gh issue comment 42 --body "Investigated — root cause is in auth middleware. Working on a fix." ``` **With curl:** ```bash curl -s -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ https://api.github.com/repos/$OWNER/$REPO/issues/42/comments \ -d '{"body": "Investigated — root cause is in auth middleware. Working on a fix."}' ``` ### Closing and Reopening **With gh:** ```bash gh issue close 42 gh issue close 42 --reason "not planned" gh issue reopen 42 ``` **With curl:** ```bash # Close curl -s -X PATCH \ -H "Authorization: token $GITHUB_TOKEN" \ https://api.github.com/repos/$OWNER/$REPO/issues/42 \ -d '{"state": "closed", "state_reason": "completed"}' # Reopen curl -s -X PATCH \ -H "Authorization: token $GITHUB_TOKEN" \ https://api.github.com/repos/$OWNER/$REPO/issues/42 \ -d '{"state": "open"}' ``` ### Linking Issues to PRs Issues are automatically closed when a PR merges with the right keywords in the body: ``` Closes #42 Fixes #42 Resolves #42 ``` To create a branch from an issue: **With gh:** ```bash gh issue develop 42 --checkout ``` **With git (manual equivalent):** ```bash git checkout main && git pull origin main git checkout -b fix/issue-42-login-redirect ``` ## 4. Issue Triage Workflow When asked to triage issues: 1. **List untriaged issues:** ```bash # With gh gh issue list --label "needs-triage" --state open # With curl curl -s \ -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/repos/$OWNER/$REPO/issues?labels=needs-triage&state=open" \ | python3 -c " import sys, json for i in json.load(sys.stdin): if 'pull_request' not in i: print(f\"#{i['number']} {i['title']}\")" ``` 2. **Read and categorize** each issue (view details, understand the bug/feature) 3. **Apply labels and priority** (see Managing Issues above) 4. **Assign** if the owner is clear 5. **Comment with triage notes** if needed ## 5. Bulk Operations For batch operations, combine API calls with shell scripting: **With gh:** ```bash # Close all issues with a specific label gh issue list --label "wontfix" --json number --jq '.[].number' | \ xargs -I {} gh issue close {} --reason "not planned" ``` **With curl:** ```bash # List issue numbers with a label, then close each curl -s \ -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/repos/$OWNER/$REPO/issues?labels=wontfix&state=open" \ | python3 -c "import sys,json; [print(i['number']) for i in json.load(sys.stdin)]" \ | while read num; do curl -s -X PATCH \ -H "Authorization: token $GITHUB_TOKEN" \ https://api.github.com/repos/$OWNER/$REPO/issues/$num \ -d '{"state": "closed", "state_reason": "not_planned"}' echo "Closed #$num" done ``` ## Quick Reference Table | 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=...` |