| name | github-issues |
| description | Use this skill whenever you need to create, read, update, close, comment on, label, assign, milestone, pin, lock, transfer, or otherwise manage GitHub Issues using the official GitHub CLI (gh). Covers the full issue lifecycle including search, bulk operations, branch development from issues, GitHub Projects integration, and the REST API escape hatch for anything gh issue subcommands don't expose directly.
|
| requires | ["gh CLI authenticated (gh auth status)","GITHUB_TOKEN or gh auth login completed","Run from inside a repo OR pass --repo OWNER/REPO to every command"] |
GitHub Issues Skill
Setup & Authentication
gh auth status
gh auth login
export GITHUB_TOKEN=ghp_xxxxxxxxxxxx
gh auth status
gh repo set-default OWNER/REPO
gh repo set-default --view
Critical Rules
- Always verify auth first. Every
gh command fails silently or with cryptic errors if GITHUB_TOKEN is missing or expired. Run gh auth status at the start of any session.
--repo is required outside a git repo. If you are not inside a cloned repository directory, pass --repo OWNER/REPO to every command.
gh issue edit replaces, not appends, for labels/assignees. Use --add-label / --remove-label and --add-assignee / --remove-assignee to make surgical changes. Using --label will overwrite all existing labels.
- Issue numbers are integers, not strings. Always pass bare numbers:
gh issue view 42, not #42.
--json output is machine-readable. Prefer --json field1,field2 --jq '.expression' over parsing human-readable output in scripts.
- Closing vs. deleting.
gh issue close is reversible. gh issue delete is permanent and requires confirmation. Never delete unless explicitly asked.
gh api is the escape hatch. Any feature not available via gh issue subcommands is reachable via gh api.
Creating Issues
Basic creation
gh issue create \
--title "Descriptive title here" \
--body "Full issue body in markdown"
Full creation with all metadata
gh issue create \
--title "Add CUPED variance reduction to experiment pipeline" \
--body-file issue-body.md \
--label "enhancement,analytics" \
--assignee "@me" \
--assignee "other-user" \
--milestone "Sprint 4" \
--project "Analytics Backlog"
Create from a template
gh issue create --web
gh issue create \
--title "Bug: Funnel drop on step 3" \
--body-file .github/ISSUE_TEMPLATE/bug_report.md
Create and immediately open in browser
gh issue create --title "Quick note" --body "..." --web
Capture the new issue number in a script
ISSUE_URL=$(gh issue create --title "..." --body "..." 2>&1 | tail -1)
ISSUE_NUM=$(echo "$ISSUE_URL" | grep -oE '[0-9]+$')
echo "Created issue #$ISSUE_NUM"
Viewing Issues
View a single issue
gh issue view 42
gh issue view 42 --comments
gh issue view 42 --web
View as JSON (preferred for agent parsing)
gh issue view 42 --json number,title,state,body,labels,assignees,milestone,comments,url,createdAt,updatedAt,closedAt,author
gh issue view 42 --json labels --jq '.labels[].name'
gh issue view 42 --json assignees --jq '[.assignees[].login]'
gh issue view 42 --json state --jq '.state'
Check your own issue status dashboard
gh issue status
gh issue status --repo OWNER/REPO
Listing & Searching Issues
Basic listing
gh issue list
gh issue list --state closed
gh issue list --state all
gh issue list --limit 50
Filter by metadata
gh issue list --label "bug"
gh issue list --label "bug" --label "priority:high"
gh issue list --assignee "@me"
gh issue list --assignee "username"
gh issue list --author "username"
gh issue list --milestone "Sprint 4"
gh issue list --mention "@me"
Full-text and GitHub search syntax
gh issue list --search "memory leak"
gh issue list --search "is:open label:bug no:assignee sort:created-asc"
gh issue list --search "is:open milestone:\"Sprint 4\" sort:updated-desc"
gh issue list --search "involves:username created:>2026-01-01"
gh search issues "CUPED variance reduction" --owner myorg
gh search issues "label:sprint-backlog" --repo OWNER/REPO
gh search issues "is:open assignee:@me" --limit 100
JSON output for scripting
gh issue list --state open --limit 100 \
--json number,title,labels,assignees \
--jq '.[] | {number, title, labels: [.labels[].name]}'
gh issue list --assignee "@me" --json number,title --jq '.[] | "#\(.number) \(.title)"'
gh issue list --state open --limit 200 \
--json labels \
--jq '[.[].labels[].name] | group_by(.) | map({label: .[0], count: length})'
Editing Issues
Safe label management (add/remove, not replace)
gh issue edit 42 --add-label "in-progress"
gh issue edit 42 --remove-label "sprint-backlog"
gh issue edit 42 --add-label "in-progress" --remove-label "sprint-backlog"
Assignee management
gh issue edit 42 --add-assignee "@me"
gh issue edit 42 --add-assignee "teammate"
gh issue edit 42 --remove-assignee "teammate"
Update title or body
gh issue edit 42 --title "Updated: more precise title"
gh issue edit 42 --body "Completely replaced body content"
gh issue edit 42 --body-file updated-body.md
Update milestone
gh issue edit 42 --milestone "Sprint 5"
gh issue edit 42 --milestone ""
Update project
gh issue edit 42 --project "Analytics Board"
gh issue edit 42 --project ""
Commenting
Add a plain comment
gh issue comment 42 --body "Status update: query is running, ETA 30 min."
Comment from a file
gh issue comment 42 --body-file standup-update.md
Edit a comment (requires comment ID — use API)
gh api repos/OWNER/REPO/issues/42/comments --jq '.[] | {id, body: .body[:80]}'
gh api repos/OWNER/REPO/issues/comments/COMMENT_ID \
--method PATCH \
--field body="Updated comment text"
Delete a comment
gh api repos/OWNER/REPO/issues/comments/COMMENT_ID --method DELETE
Closing & Reopening Issues
Close with a reason
gh issue close 42
gh issue close 42 --reason completed
gh issue close 42 --reason not_planned
gh issue close 42 --reason duplicate
Close with a final comment
gh issue close 42 \
--comment "Closing: analysis delivered to stakeholders. Outputs at s3://bucket/path."
Reopen
gh issue reopen 42
gh issue reopen 42 --comment "Reopening: discovered edge case in the data."
Label Management
Create labels for the standard agile workflow
gh label create "backlog" --color "C5DEF5" --description "In backlog, not yet scheduled"
gh label create "sprint-backlog" --color "BFD4F2" --description "Scheduled for current sprint"
gh label create "in-progress" --color "F9D0C4" --description "Actively being worked on"
gh label create "in-review" --color "FBCA04" --description "Work complete, under review"
gh label create "blocked" --color "E4E669" --description "Blocked, needs action"
gh label create "needs-clarification" --color "D4C5F9" --description "Waiting on clarification"
gh label create "done" --color "0E8A16" --description "Complete and verified"
List labels
gh label list
gh label list --json name,color,description
Edit a label
gh label edit "blocked" --description "Updated description" --color "FF0000"
Clone labels from another repo
gh label clone SOURCE_OWNER/SOURCE_REPO --repo DEST_OWNER/DEST_REPO --force
Delete a label
gh label delete "wontfix" --yes
Milestones (Epics)
Milestones are managed via the GitHub API — gh issue doesn't have milestone subcommands.
Create a milestone
gh api repos/OWNER/REPO/milestones \
--method POST \
--field title="Sprint 4" \
--field description="Two-week sprint: funnel analysis and experiment setup" \
--field due_on="2026-03-07T00:00:00Z"
List milestones
gh api repos/OWNER/REPO/milestones \
--jq '.[] | {number, title, open_issues, closed_issues, due_on, state}'
Update a milestone
gh api repos/OWNER/REPO/milestones/MILESTONE_NUMBER \
--method PATCH \
--field state="closed"
Assign an issue to a milestone by milestone number
gh api repos/OWNER/REPO/issues/42 \
--method PATCH \
--field milestone=MILESTONE_NUMBER
Branch Development from Issues
gh issue develop 42
gh issue develop 42 --name "analytics/42-funnel-drop-analysis"
gh issue develop 42 --base main --name "analytics/42-funnel-drop-analysis"
gh issue develop 42 --list
gh issue develop 42 --checkout
Pinning & Locking
Pin / Unpin (highlights issue at top of issue list)
gh issue pin 42
gh issue unpin 42
Lock / Unlock (prevent new comments)
gh issue lock 42
gh issue lock 42 --reason "off-topic"
gh issue unlock 42
Transferring Issues
gh issue transfer 42 DEST_OWNER/DEST_REPO
GitHub Projects Integration
Add issue to a project
gh project list --owner OWNER
gh project item-add PROJECT_NUMBER \
--owner OWNER \
--url "https://github.com/OWNER/REPO/issues/42"
Update project item fields (e.g. status, sprint, priority)
gh project field-list PROJECT_NUMBER --owner OWNER --format json
gh project item-edit \
--project-id PROJECT_ID \
--id ITEM_ID \
--field-id FIELD_ID \
--single-select-option-id OPTION_ID
List items in a project
gh project item-list PROJECT_NUMBER --owner OWNER --format json
Archive a completed item
gh project item-archive PROJECT_NUMBER --owner OWNER --id ITEM_ID
Bulk Operations
Close all issues with a label
gh issue list --label "wontfix" --state open --limit 200 --json number \
--jq '.[].number' | \
xargs -I{} gh issue close {} --reason not_planned
Apply a label to a batch of issues
for num in 10 11 12 13; do
gh issue edit "$num" --add-label "sprint-backlog" --remove-label "backlog"
done
Bulk comment on a set of issues
gh issue list --label "blocked" --json number --jq '.[].number' | \
while read num; do
gh issue comment "$num" --body "Sprint planning check-in: still blocked? Please update."
done
Export all open issues to JSON
gh issue list \
--state open \
--limit 500 \
--json number,title,state,labels,assignees,milestone,createdAt,updatedAt,url \
> issues-export.json
REST API Escape Hatch (gh api)
For anything not covered by gh issue subcommands, use the REST API directly:
gh api repos/OWNER/REPO/issues/NUMBER --method VERB --field key=value
gh api repos/OWNER/REPO/issues/42
gh api repos/OWNER/REPO/issues/42/timeline \
--header "Accept: application/vnd.github.mockingbird-preview+json" \
--jq '.[] | {event, created_at, actor: .actor.login}'
gh api repos/OWNER/REPO/issues/42/events \
--jq '.[] | {event, created_at, label: .label.name}'
gh api repos/OWNER/REPO/issues/42/reactions \
--jq '.[] | {user: .user.login, reaction: .content}'
gh api repos/OWNER/REPO/issues/42/reactions \
--method POST \
--field content="+1"
gh api repos/OWNER/REPO/issues/42/timeline \
--jq '.[] | select(.event=="cross-referenced") | {source_type: .source.type, number: .source.issue.number, url: .source.issue.html_url}'
gh api repos/OWNER/REPO/issues/42/subscription \
--method PUT \
--field subscribed=true \
--field ignored=false
gh api repos/OWNER/REPO/issues/42/lock \
--method PUT \
--field lock_reason="resolved"
GraphQL for complex queries
gh api graphql -f query='
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
issue(number: $number) {
number
title
state
body
labels(first: 20) { nodes { name } }
assignees(first: 10) { nodes { login } }
comments(first: 50) {
nodes { author { login } body createdAt }
}
}
}
}
' -f owner=OWNER -f repo=REPO -F number=42
gh api graphql -f query='
query($owner: String!, $repo: String!, $milestone: String!) {
repository(owner: $owner, name: $repo) {
milestone: milestones(query: $milestone, first: 1) {
nodes {
title
issues(first: 50, states: OPEN) {
nodes { number title state labels(first:10){nodes{name}} assignees(first:5){nodes{login}} }
}
}
}
}
}
' -f owner=OWNER -f repo=REPO -f milestone="Sprint 4"
Output Formatting Reference
gh issue list --json number,title,labels \
--jq '.[] | select(.labels | map(.name) | contains(["blocked"]))'
gh issue list --json number,title \
--template '{{range .}}#{{.number}}: {{.title}}{{"\n"}}{{end}}'
Workflow Patterns
Agile sprint planning sequence
gh issue list --label "backlog" --state open --limit 50 --json number,title,labels
for num in 10 15 22 31; do
gh issue edit "$num" \
--add-label "sprint-backlog" \
--remove-label "backlog" \
--milestone "Sprint 5"
done
gh issue edit 10 --add-assignee "@me"
gh issue edit 15 --add-assignee "teammate"
gh issue comment 10 --body "**Sprint Planning**
Taking this on in Sprint 5. Scope: [assumption 1, assumption 2]. Will update at start of work."
Start work on an issue
ISSUE=42
gh issue edit "$ISSUE" --add-label "in-progress" --remove-label "sprint-backlog"
gh issue develop "$ISSUE" --name "analytics/${ISSUE}-$(gh issue view $ISSUE --json title --jq '.title | ascii_downcase | gsub("[^a-z0-9]"; "-") | .[0:40]')" --checkout
gh issue comment "$ISSUE" --body "**Status Update**
- Done since last session: Sprint planning
- Doing now: Starting analysis
- Blocked: None"
Close an issue with full DoD comment
ISSUE=42
gh issue edit "$ISSUE" --add-label "done" --remove-label "in-review"
gh issue close "$ISSUE" --reason completed \
--comment "**Closing — Definition of Done verified**
- [x] Acceptance criteria met (see checklist above)
- [x] Outputs: \`s3://bucket/20260221_T42_analysis.parquet\` — 42,000 rows
- [x] Key findings: [summary]
- [x] Caveats: [any]
- Related: Unblocks #43, see also #38"
Blocked escalation
ISSUE=42
gh issue edit "$ISSUE" --add-label "blocked" --remove-label "in-progress"
gh issue comment "$ISSUE" \
--body "**BLOCKED**
- What: Cannot access \`prod_analytics.events\` table — permission denied
- Need: Read access grant from data engineering
- Who: @data-eng-lead
- Workaround: None — will move to #45 in the meantime"
Sprint review summary
gh issue list \
--milestone "Sprint 4" \
--state closed \
--json number,title,closedAt \
--jq '.[] | "#\(.number) \(.title) — closed \(.closedAt)"'
gh issue list \
--milestone "Sprint 4" \
--state open \
--json number,title,labels
Quick Reference
| Action | Command |
|---|
| Create issue | gh issue create --title "..." --body "..." |
| View issue | gh issue view NUMBER |
| View with comments | gh issue view NUMBER --comments |
| List open issues | gh issue list |
| List by label | gh issue list --label "in-progress" |
| Search issues | gh issue list --search "query" |
| Add label | gh issue edit NUMBER --add-label "label" |
| Remove label | gh issue edit NUMBER --remove-label "label" |
| Swap label | gh issue edit NUMBER --add-label "in-progress" --remove-label "sprint-backlog" |
| Assign to self | gh issue edit NUMBER --add-assignee "@me" |
| Unassign | gh issue edit NUMBER --remove-assignee "user" |
| Comment | gh issue comment NUMBER --body "..." |
| Close (completed) | gh issue close NUMBER --reason completed |
| Close with comment | gh issue close NUMBER --comment "..." |
| Reopen | gh issue reopen NUMBER |
| Create branch | gh issue develop NUMBER --name "analytics/NUMBER-slug" --checkout |
| Pin issue | gh issue pin NUMBER |
| Lock issue | gh issue lock NUMBER --reason resolved |
| Transfer issue | gh issue transfer NUMBER OWNER/REPO |
| Export to JSON | gh issue list --state all --limit 500 --json number,title,state,labels,url |
| Raw API call | gh api repos/OWNER/REPO/issues/NUMBER |