Testland
Browse all skills & agents

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.

Install with skills.sh (any agent)

npx skills add testland/qa --skill jira-bug-workflow-runner
View source

jira-bug-workflow-runner

Overview

Jira's workflow engine maps cleanly to the canonical defect lifecycle (bug-lifecycle-reference) but every project's actual workflow is configurable, so the runner has to look up transition IDs at runtime rather than hard-code them.

This skill wraps the Jira Cloud REST API v3 (per developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/ (opens in new window)) for the four core operations: create, transition, update, and search.

When to use

  • Filing a bug from a CI test failure (consumed by bug-report-from-failure).
  • Bulk-transitioning bugs after a release (e.g., move all VerifiedClosed after deployment).
  • Building a triage script that pulls New defects and applies severity / priority based on labels.
  • Backing a duplicate-defect search backend.

Authoring

Authentication

Per Atlassian docs, Jira Cloud REST API v3 uses HTTP Basic auth with an API token:

export JIRA_BASE="https://your-tenant.atlassian.net"
export JIRA_EMAIL="you@company.com"
export JIRA_TOKEN="<api-token-from-id.atlassian.com>"
export JIRA_AUTH=$(echo -n "$JIRA_EMAIL:$JIRA_TOKEN" | base64)
import requests, base64, os

auth = base64.b64encode(
    f"{os.environ['JIRA_EMAIL']}:{os.environ['JIRA_TOKEN']}".encode()
).decode()
HEADERS = {
    "Authorization": f"Basic {auth}",
    "Accept": "application/json",
    "Content-Type": "application/json",
}
BASE = os.environ["JIRA_BASE"]

Create a bug

POST /rest/api/3/issue per the API group docs. The description must be Atlassian Document Format (ADF), not plain text.

def create_bug(project_key, summary, description_text, severity, priority, labels):
    payload = {
        "fields": {
            "project": {"key": project_key},
            "summary": summary,
            "description": {
                "type": "doc",
                "version": 1,
                "content": [{
                    "type": "paragraph",
                    "content": [{"type": "text", "text": description_text}],
                }],
            },
            "issuetype": {"name": "Bug"},
            "priority": {"name": priority},   # e.g. "High"
            "labels": labels + [f"severity-{severity}"],
        }
    }
    r = requests.post(f"{BASE}/rest/api/3/issue", json=payload, headers=HEADERS)
    r.raise_for_status()
    return r.json()["key"]

Note: severity is typically a custom field - most tenants either define a custom Severity field (customfield_XXXXX) or use labels (severity-critical). The example above uses labels for portability; discovering and submitting the custom field is in references/jira-rest-api.md.

Look up and apply a transition

Workflow transitions are project-specific. Look up the available transitions then apply by transition ID:

def get_transitions(issue_key):
    r = requests.get(f"{BASE}/rest/api/3/issue/{issue_key}/transitions",
                     headers=HEADERS)
    r.raise_for_status()
    return r.json()["transitions"]

def transition(issue_key, target_state_name):
    transitions = get_transitions(issue_key)
    match = next((t for t in transitions if t["name"] == target_state_name), None)
    if not match:
        raise ValueError(f"No transition named {target_state_name}; "
                         f"available: {[t['name'] for t in transitions]}")
    r = requests.post(
        f"{BASE}/rest/api/3/issue/{issue_key}/transitions",
        json={"transition": {"id": match["id"]}},
        headers=HEADERS,
    )
    r.raise_for_status()

The POST /rest/api/3/issue/{key}/transitions body shape is {"transition": {"id": "<id>"}} per the API group docs.

Search via JQL

POST /rest/api/3/search/jql returns issues matching a JQL query. Useful for duplicate detection and triage queues.

def search_jql(jql, max_results=50):
    r = requests.post(
        f"{BASE}/rest/api/3/search/jql",
        json={"jql": jql, "fields": ["summary", "status", "priority"],
              "maxResults": max_results},
        headers=HEADERS,
    )
    r.raise_for_status()
    return r.json()["issues"]

# Triage queue:
triage = search_jql(
    'project = ENG AND issuetype = Bug AND status = "New" ORDER BY created ASC'
)

# Duplicate-candidate search:
dupes = search_jql(
    f'project = ENG AND text ~ "{summary_safe}" AND issuetype = Bug'
)

Running

Idempotent bug creation

CI bug filing must not duplicate if the same failure recurs. Pair with upstream duplicate detection, but also defensively search before creating:

def create_or_attach(project, summary, body):
    existing = search_jql(
        f'project = {project} AND summary ~ "\\"{summary}\\"" '
        f'AND statusCategory != Done',
        max_results=5,
    )
    if existing:
        # Attach a comment to the existing bug instead of duplicating
        key = existing[0]["key"]
        add_comment(key, f"Recurred at {timestamp()}: {body[:500]}")
        return key
    return create_bug(project, summary, body, "Medium", "Medium",
                      labels=["auto-filed", "ci-failure"])

Verify: the statusCategory != Done search must run and return 0 open matches before create_bug fires. If it returns a hit, comment on that key instead of creating; if the search itself errors, fail closed (skip the create and surface the error) rather than filing a possible duplicate.

Bulk transition after release

Dry-run first: a mis-scoped JQL can push hundreds of issues into the wrong state, and a transition is not trivially reversible (bulk transitions without dry-run in Anti-patterns). Gate the apply behind a flag:

DRY_RUN = True  # flip to False only after reviewing the logged plan

verified = search_jql(
    'project = ENG AND status = Verified AND fixVersion = "2026.05.20"',
    max_results=1000,
)
for issue in verified:
    if DRY_RUN:
        print(f"[dry-run] {issue['key']}: Verified -> Close Issue")
        continue
    transition(issue["key"], "Close Issue")

Verify: assert the dry-run count and keys match the issue set you intended to close before flipping DRY_RUN to False; if they do not, fix the JQL and re-run the dry run. transition already raises ValueError when the named transition is absent for an issue's workflow, so a workflow mismatch fails loud rather than silently skipping.

CI integration

Auto-file a bug from a test failure:

# .github/workflows/test.yml (excerpt)
- name: Run tests
  id: tests
  run: pytest --junitxml=results.xml
  continue-on-error: true

- name: File Jira bug on failure
  if: steps.tests.outcome == 'failure'
  env:
    JIRA_BASE: ${{ secrets.JIRA_BASE }}
    JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }}
    JIRA_TOKEN: ${{ secrets.JIRA_TOKEN }}
  run: python scripts/file-jira-bug.py results.xml

Where file-jira-bug.py parses the JUnit XML, extracts the failure, deduplicates, and creates / comments per the helpers above.

Verify: assert the create call returned HTTP 2xx and a non-empty issue key before the step reports success. On 400, the description was likely plain text instead of ADF or a required field is missing - fix the payload and re-run. On 429 (rate limit), back off and retry rather than failing the build. If it still fails, leave the test result red so the filing gap stays visible instead of being swallowed.

Anti-patterns

Anti-patternWhy it failsFix
Hard-coding transition IDsWorkflow updates break the runner silentlyLook up transitions per call via GET /transitions
Plain-text descriptionAPI returns 400 - Jira v3 requires ADFWrap as {"type": "doc", "version": 1, "content": [...]}
No deduplication before createEach retry of a flaky test creates a new bugSearch by summary first; comment on existing
Severity as built-in priorityConflates two axes (severity-vs-priority-reference)Use custom Severity field or severity-* labels
Storing the API token in codeToken leakUse environment variables / secret stores
Polling /transitions on every callRate-limitedCache per workflow scheme, refresh on 4xx
Bulk transitions without dry-runCannot easily reverse if wrong stateAlways run in dry-run mode first; log all changes

Limitations

  • Workflow is per-project. A transition name in one project may not exist in another - the runner must handle "transition not found" gracefully.
  • Custom fields are tenant-specific. Field IDs (customfield_10039) vary; discover at deploy time.
  • ADF complexity. Rich descriptions (code blocks, tables) need full ADF construction - see Atlassian's ADF reference.
  • Rate limits. Jira Cloud rate limits per minute; bulk operations need throttling.
  • JQL injection. text ~ "user input" accepts JQL operators - always escape quotes and reserved characters.

References

  • Jira Cloud REST API v3 issues - developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/ (opens in new window).
  • Jira Cloud authentication - developer.atlassian.com/cloud/jira/platform/basic-auth-for-rest-apis.
  • ADF spec - developer.atlassian.com/cloud/jira/platform/apis/document/structure.
  • JQL syntax - confluence.atlassian.com/jiracoreserver/advanced-searching.
  • Severity custom field, field updates, and result parsing: references/jira-rest-api.md.
  • Sibling references: bug-lifecycle-reference, severity-vs-priority-reference.
  • Sibling skills: linear-bug-workflow-runner, github-issues-bug-workflow.
  • Consumed by: bug-report-from-failure.
  • Sibling-plugin neighbour: testrail-integration (in the qa-test-reporting plugin) - different scope (test-result posting; not bug workflow).

Jira Cloud REST API v3 - field details

View source (opens in new window)

Jira Cloud REST API v3 - field details

Detailed field operations for jira-bug-workflow-runner. Auth setup, create, transition, and JQL search stay in SKILL.md; this file holds the custom-field, update, and result-parsing detail.

Severity custom field

severity is usually a custom field, not the built-in priority. Discover the field ID once per tenant:

curl -u "$JIRA_EMAIL:$JIRA_TOKEN" \
     "$JIRA_BASE/rest/api/3/field" \
     | jq '.[] | select(.name=="Severity") | {id, name}'
# {"id": "customfield_10039", "name": "Severity"}

Then submit it in the create payload:

"customfield_10039": {"value": severity},  # "Critical" | "High" | ...

Update fields

PUT /rest/api/3/issue/{key} for arbitrary field updates:

def update_priority(issue_key, priority_name):
    r = requests.put(
        f"{BASE}/rest/api/3/issue/{issue_key}",
        json={"fields": {"priority": {"name": priority_name}}},
        headers=HEADERS,
    )
    r.raise_for_status()

Parsing results

create_bug returns the new issue key (e.g. ENG-12345). Build a permalink:

url = f"{BASE}/browse/{issue_key}"

Search responses include expand, total, startAt, and issues (the array). Always check total against maxResults for pagination.

Field-spec notes

  • ADF descriptions. Rich descriptions (code blocks, tables) need full ADF construction - see developer.atlassian.com/cloud/jira/platform/apis/document/structure.
  • JQL injection. text ~ "user input" accepts JQL operators - always escape quotes and reserved characters before interpolating.

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?).

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.

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.