github-issues-bug-workflow
Author and run GitHub Issues bug workflows via REST API (2026-03-10): issue creation, state changes (open / closed with `state_reason`), label-based severity/priority classification, and comment attachment. Covers `POST /repos/{owner}/{repo}/issues`, `PATCH` for `state_reason` transitions (completed / not_planned / duplicate / reopened), and label conventions for GitHub's binary open/closed model; Projects v2, `gh` CLI, and CI wiring live in references/. Use when programmatically managing the GitHub Issues bug lifecycle; for the same workflow on another tracker use azuredevops-bug-workflow, jira-bug-workflow-runner, or linear-bug-workflow-runner.
Install with skills.sh (any agent)
npx skills add testland/qa --skill github-issues-bug-workflowgithub-issues-bug-workflow
Overview
GitHub Issues has only two states: open and closed. To express the canonical defect lifecycle (bug-lifecycle-reference) teams supplement Issues with labels (severity, priority, status) and optionally Projects v2 (status columns).
This skill wraps the GitHub Issues REST API (per docs.github.com/en/rest/issues/issues (opens in new window)) for create / update / close / reopen / search, and notes the Projects v2 GraphQL augmentation when richer state is needed. Version-sensitive facts are collected under API version.
When to use
How to use
Authentication
Per GitHub REST API docs:
export GITHUB_TOKEN="ghp_..." # personal access token, classic or fine-grained
export GITHUB_REPO="owner/repo"import requests, os
HEADERS = {
"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2026-03-10",
}
BASE = f"https://api.github.com/repos/{os.environ['GITHUB_REPO']}"API version
Version-sensitive facts, kept in one place so time-bound detail does not leak through the rest of the doc:
Create an issue
POST /repos/{owner}/{repo}/issues per the API docs:
def create_bug(title, body, severity, priority, labels=None):
payload = {
"title": title,
"body": body,
"labels": (labels or []) + [
"bug",
f"severity:{severity}",
f"priority:{priority}",
],
}
r = requests.post(f"{BASE}/issues", json=payload, headers=HEADERS)
r.raise_for_status()
return r.json()Required parameter is title. Optional: body, assignees, milestone, labels, type (see API version).
Label conventions
Since GitHub has no first-class severity / priority field, teams adopt label prefixes:
| Convention | Example labels |
|---|---|
| Severity | severity:critical, severity:high, severity:medium, severity:low, severity:trivial |
| Priority | priority:p1, priority:p2, priority:p3, priority:p4, priority:p5 |
| Lifecycle | status:triage, status:confirmed, status:in-progress, status:in-review, status:verified, status:wontfix, status:duplicate |
| Defect type | type:regression, type:performance, type:security |
| Component | component:auth, component:payments, component:ui |
Adopt them consistently - defect-report review checks that severity + priority labels are both present.
State transitions via PATCH
PATCH /repos/{owner}/{repo}/issues/{issue_number}. The state_reason parameter (per API docs) takes completed | not_planned | reopened | duplicate:
def close(issue_number, reason="completed"):
"""reason: completed | not_planned | duplicate"""
r = requests.patch(
f"{BASE}/issues/{issue_number}",
json={"state": "closed", "state_reason": reason},
headers=HEADERS,
)
r.raise_for_status()
result = r.json()
# verify the destructive transition landed; a stale state means a concurrent edit won
assert result["state"] == "closed" and result["state_reason"] == reason, result
return result
def reopen(issue_number):
r = requests.patch(
f"{BASE}/issues/{issue_number}",
json={"state": "open", "state_reason": "reopened"},
headers=HEADERS,
)
r.raise_for_status()
return r.json()Map canonical lifecycle states via labels + close-reason:
| Canonical | GitHub representation |
|---|---|
| New | open + status:triage |
| Open / Acknowledged | open + status:confirmed |
| Assigned | open + status:confirmed + assignees set |
| In Progress | open + status:in-progress + linked draft PR |
| Fixed | open + status:in-review + ready PR |
| Verified | open + status:verified |
| Closed (success) | closed + state_reason: completed |
| Reopened | open + state_reason: reopened |
| Deferred / Wontfix | closed + state_reason: not_planned + label status:wontfix |
| Rejected | closed + state_reason: not_planned + label not-a-bug |
| Duplicate | closed + state_reason: duplicate + comment Duplicate of #N |
Search
GET /repos/{owner}/{repo}/issues supports filter via query parameters; for richer search use the search endpoint:
def search_issues(q):
r = requests.get(
"https://api.github.com/search/issues",
params={"q": f"repo:{os.environ['GITHUB_REPO']} {q}"},
headers=HEADERS,
)
r.raise_for_status()
return r.json()["items"]
dupes = search_issues(
f'type:issue is:open label:bug "{title_safe}" in:title,body'
)GitHub search has a 30-request-per-minute unauthenticated / higher authenticated rate limit.
Comments
POST /repos/{owner}/{repo}/issues/{issue_number}/comments:
def add_comment(issue_number, body):
r = requests.post(
f"{BASE}/issues/{issue_number}/comments",
json={"body": body}, headers=HEADERS)
r.raise_for_status()
return r.json()Worked example
File a bug from a CI failure idempotently: search for an open duplicate first, comment on it if found, otherwise create a new labelled issue. This reuses search_issues, add_comment, and create_bug above:
def create_or_attach(title, body):
dupes = search_issues(f'is:open label:bug "{title}" in:title')
if dupes:
add_comment(dupes[0]["number"], f"Recurred: {body[:500]}")
return dupes[0]["number"]
issue = create_bug(title, body, severity="medium", priority="p3")
return issue["number"]
# From a failing pytest run:
num = create_or_attach(
"Checkout fails for promo X",
"<repro>\n1. Apply promo X\n2. Checkout 500s\n</repro>",
)
print(f"Bug tracked as #{num}")Verify: search_issues ranks by relevance and can return near-misses, so before attaching to or bulk-closing a hit, assert its title matches the target; skip and log any that do not rather than commenting on or closing the wrong issue, then re-run the dedupe against the corrected query.
The create response includes number (per-repo), html_url (permalink), and node_id (GraphQL ID for Projects v2 cross-ref). Moving the issue across a Projects v2 status column, the gh CLI equivalents, and CI wiring are in references/github-issues-reference.md.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Closing without state_reason | Defaults to completed - wrong for not-a-bug / duplicate | Always set state_reason explicitly |
| Severity / priority in title prefix | "[CRITICAL]" prefixes - not searchable; not filterable | Use labels |
| Free-form status labels per team | Cross-team queries break | Adopt the canonical label vocabulary above |
| Search-rate-limit ignored | Bulk dedupe scripts get 403s | Throttle to 30 req/min unauth, 5000 authenticated |
No X-GitHub-Api-Version header | Future API changes silently break code | Always set the version header |
| Plain-text body (no Markdown) | Loses code-block formatting | Use Markdown in body |
Closing with state: closed without state_reason for "wontfix" | Ambiguous closure - looks the same as a fix | Use state_reason: not_planned |
Limitations
References
GitHub Issues deep reference
View source (opens in new window)GitHub Issues deep reference
Deep reference for github-issues-bug-workflow SKILL.md. Consult when moving an issue across a Projects v2 status column (GraphQL), scripting the workflow with the gh CLI, or wiring GitHub Actions to file an issue on test failure.
Parsing results
Create response includes number (per-repo), html_url (permalink), node_id (GraphQL ID for Projects v2 cross-ref).
Search response includes items array (issues + PRs), total_count, incomplete_results (set to true on partial results due to rate limit).
Projects v2 status updates
For richer state (e.g., a Kanban with custom columns), Projects v2 requires GraphQL - the REST API doesn't reach Projects v2:
PROJECTS_MUTATION = """
mutation MoveItem($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
updateProjectV2ItemFieldValue(
input: { projectId: $projectId, itemId: $itemId,
fieldId: $fieldId, value: { singleSelectOptionId: $optionId } }
) { projectV2Item { id } }
}
"""
# Discovery of projectId, itemId, fieldId, optionId via the matching queries.Per docs.github.com/en/issues/planning-and-tracking-with-projects.
gh CLI for scripts
The gh CLI handles auth via the user's stored credentials, so scripted workflows skip token wiring (per cli.github.com/manual/gh_issue):
# Create
gh issue create \
--title "Checkout fails for promo X" \
--body-file failure.md \
--label bug,severity:high,priority:p2
# Close with reason
gh issue close 1234 --reason completed
gh issue close 1234 --reason "not planned"
# Search
gh issue list --search 'is:open label:bug "checkout fails"'CI integration
# .github/workflows/test.yml
- name: Run tests
id: tests
run: pytest --junitxml=results.xml
continue-on-error: true
- name: File issue on test failure
if: steps.tests.outcome == 'failure'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPO: ${{ github.repository }}
run: python scripts/file-github-bug.py results.xmlUse the auto-provided GITHUB_TOKEN for in-repo automation; for cross-repo, use a fine-grained PAT.
Related skills
azuredevops-bug-workflow
Authors and triages Bug work items in Azure DevOps Boards via the Work Item Tracking REST API (api-version 7.1) - Bug creation with JSON Patch, state transitions across New/Active/Resolved/Closed, and WIQL queries for triage queues and duplicate detection. Deep operational blocks (field-value fetch, PR / build artifact links, bulk close, az boards CLI, CI wiring) live in references/. Use when programmatically managing Azure DevOps Bug lifecycle states: creating from CI failures, triaging open defect queues, transitioning states in bulk, or attaching traceability links to builds and pull requests.
bug-lifecycle-reference
Pure-reference catalog of defect lifecycle states and transitions. Defines the ISTQB-canonical states (new / open / assigned / in-progress / fixed / verified / closed / reopened / deferred / rejected / duplicate) and the transitions between them, distinguishes the ISTQB terms (error → fault / defect → failure), maps the lifecycle to the standard Jira / Linear / GitHub Issues workflows, and cites IEEE 1044-2009 and ISO/IEC/IEEE 29119-3 for the canonical anchors. Use as the lifecycle vocabulary for bug-report review, duplicate detection, and the platform-workflow skills.
bug-report-from-failure
On-demand builder that converts a SINGLE test failure record (JUnit XML, Allure JSON, pytest --tb=short, Playwright HTML, Cypress mocha-junit) into a structured, tracker-agnostic bug SPEC: extracts test name, assertion, stack trace, environment, and artefacts, and proposes severity, defect type (IEEE 1044), and a root-cause hypothesis (ISTQB CTAL-TA), then hands the JSON spec to a jira/linear/github-issues-bug-workflow runner to file. Use when you already hold a failure artefact and want one classified, ready-to-file report. Distinct from the event-driven CI orchestrator that triggers automatically on a pipeline failure and files in bulk, and from screen-recording-driven bug reporting; this is the on-demand, single-record spec builder.
confirmation-testing-workflow
Procedure for proving that a claimed defect fix actually reached the build under test and actually works. Covers the merge-base ancestry check that proves the running build contains the fix commit rather than trusting a version label, the priority order for choosing which reproduction to re-run, and the VERIFIED / NOT FIXED / BLOCKED verdict table whose governing rule is that any ambiguous, flaky, or unreproducible result resolves to BLOCKED and is never guessed. Scoped to ISTQB confirmation testing (does this specific fix work?), not regression testing (did the fix break something else?), and not triage or severity assignment. Use when a developer has marked a defect Fixed and someone must decide whether it moves to Verified or back to Reopened.
defect-taxonomy-istqb
Pure-reference catalog of defect categorisation taxonomies. Covers the IEEE 1044-2009 anomaly classification (anomaly class, anomaly type, anomaly severity, root cause category), the ISTQB CTAL-TA root-cause taxonomy (requirements / design / implementation / interface / test-data / build-environment), and the Orthogonal Defect Classification (ODC) eight-attribute framework. Maps each taxonomy to a worked example showing how the same defect classifies under each. Use to categorise defects consistently across a team, drive root-cause analysis, and inform process-improvement decisions (where in the SDLC do we leak the most defects?).
jira-bug-workflow-runner
Jira Cloud bug workflow runner using the REST API v3: issue creation with an ADF description, runtime transition lookup and apply, JQL search for triage queues and duplicate detection, severity/priority field updates, label-based classification (severity/priority/regression), and idempotent CI-driven filing from JUnit XML test failures. Use when the target tracker is Jira Cloud and the task involves Jira lifecycle states (create, triage, transition, close). Distinct from a platform-agnostic event-driven CI defect filer, and from linear-bug-workflow-runner / github-issues-bug-workflow for other trackers.
linear-bug-workflow-runner
Author and run Linear bug workflows via the GraphQL API: issue creation, state transitions (workflowState assignment), priority assignment (0 No priority / 1 Urgent / 2 High / 3 Medium / 4 Low), label-based classification, search by team and content. Covers the issueCreate mutation, issueUpdate for state transitions, the workflowStates query for per-team state IDs, and Linear's API-key vs OAuth Bearer auth modes; resolve-by-type, CI wiring, and result parsing live in references/. Use when the target tracker is Linear specifically; for other trackers use jira-bug-workflow-runner (Jira) or github-issues-bug-workflow (GitHub Issues). Files and transitions the issue; reproducing the defect is a separate concern.
severity-vs-priority-reference
Pure-reference catalog distinguishing defect severity (impact on the system / user) from defect priority (urgency of fix), each on its own axis. Enumerates the canonical 5-point severity scale (Critical / High / Medium / Low / Trivial) and the 5-point priority scale (Immediate / High / Medium / Low / Deferred), explains why they must be tracked separately (a Critical/Deferred legacy bug exists; a Trivial/Immediate spelling bug on the homepage exists), maps to IEEE 1044-2009 severity classes, and ties to bug-lifecycle-reference state transitions. Use when triaging a defect, configuring a tracker's severity/priority fields, or reviewing whether a bug report assigned them consistently.