Testland
Browse all skills & agents

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.

Install with skills.sh (any agent)

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

azuredevops-bug-workflow

Overview

Azure DevOps Boards models defects as Bug work items with a fixed set of built-in states (bug-lifecycle-reference). The canonical state sequence for the Agile process template is New -> Active -> Resolved -> Closed, per learn.microsoft.com/en-us/azure/devops/boards/work-items/guidance/agile-process-workflow (opens in new window).

All mutations use the Work Item Tracking REST API (api-version 7.1) with a JSON Patch body, per learn.microsoft.com/en-us/rest/api/azure/devops/wit/work-items/create (opens in new window).

When to use

  • Filing a Bug from a CI test failure (consumed by bug-report-from-failure).
  • Building a triage script that pulls New defects and applies priority or assigns to the on-call engineer.
  • Bulk-transitioning Resolved bugs to Closed after a release.
  • Backing a duplicate-detection search backend for ADO-using teams.

How to use

  1. Authenticate with a PAT via HTTP Basic and set Content-Type: application/json-patch+json (see Authentication).
  2. Before creating, dedupe against open Bugs with a WIQL CONTAINS WORDS query (see Search via WIQL).
  3. Create the Bug with a JSON Patch document, then transition its System.State as the fix progresses (see Create / Transition).
  4. Attach traceability links, batch-fetch fields, bulk-close, or wire CI - all in references/azure-devops-wit-reference.md.

Authentication

The API supports Personal Access Tokens (PAT) via HTTP Basic auth, where the username is empty and the password is the PAT. Scope required: vso.work_write (grants read, create, and update of work items), per the Work Items - Create security docs (opens in new window).

export ADO_ORG="https://dev.azure.com/my-org"
export ADO_PROJECT="MyProject"
export ADO_PAT="<personal-access-token>"
export ADO_AUTH=$(echo -n ":$ADO_PAT" | base64)
import requests, base64, os

pat = os.environ["ADO_PAT"]
token = base64.b64encode(f":{pat}".encode()).decode()
HEADERS = {
    "Authorization": f"Basic {token}",
    "Content-Type": "application/json-patch+json",
    "Accept": "application/json",
}
BASE = os.environ["ADO_ORG"]
PROJECT = os.environ["ADO_PROJECT"]

Note: the Content-Type for all write operations is application/json-patch+json, not application/json. Sending application/json returns HTTP 415.

Create a Bug

POST {org}/{project}/_apis/wit/workitems/$Bug?api-version=7.1 per learn.microsoft.com/en-us/rest/api/azure/devops/wit/work-items/create (opens in new window). The body is a JSON Patch document (array of operations).

Core Bug fields per the Agile process template (learn.microsoft.com/en-us/azure/devops/boards/work-items/guidance/agile-process-workflow (opens in new window)):

Field reference namePurposeExample value
System.TitleSummary"Login fails on SSO redirect"
System.DescriptionRepro steps (HTML)"<b>Steps:</b><ol>..."
Microsoft.VSTS.Common.PriorityPriority 1-42
Microsoft.VSTS.Common.SeveritySeverity (process-defined)"2 - High"
System.AssignedToTriage assignee"user@company.com"
System.TagsLabels"ci-failure; regression"
def create_bug(title, description_html, priority, severity, tags=""):
    body = [
        {"op": "add", "path": "/fields/System.Title", "value": title},
        {"op": "add", "path": "/fields/System.Description",
         "value": description_html},
        {"op": "add", "path": "/fields/Microsoft.VSTS.Common.Priority",
         "value": priority},
        {"op": "add", "path": "/fields/Microsoft.VSTS.Common.Severity",
         "value": severity},
        {"op": "add", "path": "/fields/System.Tags", "value": tags},
    ]
    r = requests.post(
        f"{BASE}/{PROJECT}/_apis/wit/workitems/$Bug?api-version=7.1",
        json=body, headers=HEADERS,
    )
    r.raise_for_status()
    item = r.json()
    return item["id"], item["url"]

The response id field is the work item integer ID. Construct the browser URL as {BASE}/{PROJECT}/_workitems/edit/{id}.

Transition state (PATCH)

State transitions use PATCH {org}/{project}/_apis/wit/workitems/{id}?api-version=7.1 with a replace or add operation on /fields/System.State, per learn.microsoft.com/en-us/rest/api/azure/devops/wit/work-items/update (opens in new window).

Unlike Jira, ADO has no separate "transitions" endpoint. Set the target state string directly. Valid values for the Agile Bug type are New, Active, Resolved, and Closed.

The optional test op on /rev provides optimistic concurrency: the server rejects the patch if the revision no longer matches, preventing lost updates.

def set_state(work_item_id, target_state, current_rev=None):
    body = []
    if current_rev is not None:
        body.append({"op": "test", "path": "/rev", "value": current_rev})
    body.append(
        {"op": "add", "path": "/fields/System.State", "value": target_state}
    )
    r = requests.patch(
        f"{BASE}/{PROJECT}/_apis/wit/workitems/{work_item_id}?api-version=7.1",
        json=body, headers=HEADERS,
    )
    r.raise_for_status()
    return r.json()["fields"]["System.State"]

To also record who resolved and why:

body += [
    {"op": "add", "path": "/fields/Microsoft.VSTS.Common.ResolvedReason",
     "value": "Fixed"},
    {"op": "add", "path": "/fields/System.History",
     "value": "Resolved in PR #1234"},
]

Search via WIQL

POST {org}/{project}/_apis/wit/wiql?api-version=7.1 runs a Work Item Query Language expression, per learn.microsoft.com/en-us/rest/api/azure/devops/wit/wiql/query-by-wiql (opens in new window). The request body is {"query": "<WIQL string>"}. The response workItems array contains {id, url} objects; batch-fetch field values with a second call (see references/azure-devops-wit-reference.md).

def wiql_search(query, top=50):
    r = requests.post(
        f"{BASE}/{PROJECT}/_apis/wit/wiql?$top={top}&api-version=7.1",
        json={"query": query},
        headers={**HEADERS, "Content-Type": "application/json"},
    )
    r.raise_for_status()
    return r.json().get("workItems", [])   # [{id, url}, ...]

# Triage queue: all open Bugs ordered by priority then created date
triage_items = wiql_search(
    "SELECT [System.Id] FROM WorkItems "
    "WHERE [System.WorkItemType] = 'Bug' "
    "AND [System.State] NOT IN ('Resolved', 'Closed') "
    "ORDER BY [Microsoft.VSTS.Common.Priority] ASC, "
    "[System.CreatedDate] DESC"
)

# Duplicate candidate search
dupes = wiql_search(
    f"SELECT [System.Id] FROM WorkItems "
    f"WHERE [System.WorkItemType] = 'Bug' "
    f"AND [System.Title] CONTAINS WORDS 'SSO redirect' "
    f"AND [System.State] <> 'Closed'"
)

WIQL CONTAINS WORDS is a full-text operator. Do not substitute user input directly; sanitise by stripping WIQL reserved characters ([, ], ') before interpolating into the query string.

Worked example

File a Bug from a nightly pytest failure, deduped, then resolve it after the fix ships. This reuses create_bug, wiql_search, and set_state above:

# 1. Extract the failure (title + HTML repro) from the JUnit XML.
title = "Login fails on SSO redirect"
repro = "<b>Steps:</b><ol><li>Sign in via SSO</li><li>Redirect 500s</li></ol>"

# 2. Dedupe: search open Bugs by title before creating.
hits = wiql_search(
    "SELECT [System.Id] FROM WorkItems "
    "WHERE [System.WorkItemType] = 'Bug' "
    f"AND [System.Title] CONTAINS WORDS '{title}' "
    "AND [System.State] <> 'Closed'", top=5)

# 3. Create only if no open duplicate exists; otherwise comment on the
#    existing item (add_comment lives in references).
if hits:
    bug_id = hits[0]["id"]
else:
    bug_id, _ = create_bug(title, repro, priority=2, severity="2 - High",
                           tags="ci-failure; regression")
    print(f"Filed {BASE}/{PROJECT}/_workitems/edit/{bug_id}")

# 4. After the fix merges, transition New/Active -> Resolved.
set_state(bug_id, "Resolved")   # add ResolvedReason + History as shown above

The packaged idempotent CI filer (create_or_comment + add_comment), plus bulk close, artifact links, and pipeline wiring, are in references/azure-devops-wit-reference.md.

Anti-patterns

Anti-patternWhy it failsFix
Sending Content-Type: application/json on create/updateAPI returns HTTP 415Use application/json-patch+json for all PATCH/POST to workitems
Hard-coding state strings like "In Progress"State names are process-template-specific (Agile vs Scrum vs CMMI)Verify state names for the target project's process template before automating
No test /rev op on concurrent updatesOverwrites changes made between read and writeAdd {"op": "test", "path": "/rev", "value": rev} as the first operation
Direct WIQL string interpolation of user inputWIQL injection via reserved chars (', [, ])Strip or escape reserved characters before interpolating
Ignoring $top truncation on WIQL responsesSilently misses items when the queue exceeds the capCheck len(hits) == top; paginate or increase $top (max 20 000)
Building vstfs:/// URIs without project/repo GUIDsArtifact links silently fail or link to the wrong targetFetch projectId and repoId from the Repos API first
Creating a Bug per flaky-test recurrencePollutes the backlogAlways comment on the existing open bug instead of filing a new one
Storing the PAT in source codeToken leakUse environment variables or Azure Key Vault secret references

Limitations

  • Process-template state names vary. Agile uses Active; Scrum uses Committed; CMMI uses Active with additional substates. Query the process template for the project before hard-coding state strings.
  • Severity field depends on process template. Agile and CMMI include Microsoft.VSTS.Common.Severity; Scrum does not by default. Use GET /_apis/wit/workitemtypes/Bug/fields to verify field availability.
  • Comments API is preview. The workItems/{id}/comments endpoint is api-version 7.1-preview.3 and its contract may change.
  • Artifact link URIs require project and repo GUIDs. Display names are not accepted; resolve them via GET /_apis/projects and GET /_apis/git/repositories first.
  • Rate limits. Azure DevOps Services enforces per-user and per-IP throttling; bulk operations need retry-with-backoff on HTTP 429.

References

Azure DevOps WIT deep reference

View source (opens in new window)

Azure DevOps WIT deep reference

Deep reference for azuredevops-bug-workflow SKILL.md. Consult when batch-fetching field values after a WIQL query, packaging idempotent CI filing, attaching PR / build artifact links, bulk-closing after a release, driving the az boards CLI, or wiring an Azure Pipeline to file bugs on test failure.

Fetch field values after a WIQL query

The WIQL response only returns IDs. Batch-fetch fields with a second call to GET /_apis/wit/workitems?ids=...:

def get_work_items(ids, fields=None):
    if not ids:
        return []
    fields_param = ",".join(fields) if fields else (
        "System.Id,System.Title,System.State,"
        "Microsoft.VSTS.Common.Priority,Microsoft.VSTS.Common.Severity"
    )
    ids_param = ",".join(str(i["id"]) for i in ids)
    r = requests.get(
        f"{BASE}/_apis/wit/workitems"
        f"?ids={ids_param}&fields={fields_param}&api-version=7.1",
        headers={**HEADERS, "Content-Type": "application/json"},
    )
    r.raise_for_status()
    return r.json()["value"]

get_work_items returns the full field map per item under value[].fields.

Idempotent bug creation from CI (packaged)

Search before creating to prevent duplicate defects (per the canonical defect lifecycle guidance in bug-lifecycle-reference):

def create_or_comment(title, body_html, priority="2", severity="2 - High"):
    hits = wiql_search(
        f"SELECT [System.Id] FROM WorkItems "
        f"WHERE [System.WorkItemType] = 'Bug' "
        f"AND [System.Title] CONTAINS WORDS '{title[:60]}' "
        f"AND [System.State] <> 'Closed'",
        top=5,
    )
    if hits:
        existing_id = hits[0]["id"]
        add_comment(existing_id, f"Recurred: {body_html[:500]}")
        return existing_id, False   # (id, created)
    item_id, _ = create_bug(title, body_html, priority, severity,
                            tags="ci-failure; auto-filed")
    return item_id, True

def add_comment(work_item_id, text_html):
    r = requests.post(
        f"{BASE}/{PROJECT}/_apis/wit/workItems/{work_item_id}"
        f"/comments?api-version=7.1-preview.3",
        json={"text": text_html},
        headers={**HEADERS, "Content-Type": "application/json"},
    )
    r.raise_for_status()

The comments endpoint is api-version 7.1-preview.3 and its contract may change.

Linking to PRs and builds

Work item relations are attached via a PATCH operation with op: add on /relations/-, per learn.microsoft.com/en-us/rest/api/azure/devops/wit/work-items/update (opens in new window) (see the "Add a link" example in the API docs). The relation value object contains rel (link type name) and url (target URL).

Link another work item as related:

def link_related(source_id, target_id, comment=""):
    target_url = f"{BASE}/_apis/wit/workItems/{target_id}"
    body = [{
        "op": "add",
        "path": "/relations/-",
        "value": {
            "rel": "System.LinkTypes.Related",
            "url": target_url,
            "attributes": {"comment": comment},
        }
    }]
    r = requests.patch(
        f"{BASE}/{PROJECT}/_apis/wit/workitems/{source_id}?api-version=7.1",
        json=body, headers=HEADERS,
    )
    r.raise_for_status()

Link a pull request or build (artifact link). The url for artifact links uses the vstfs:/// URI scheme. For a Git pull request the relation type is ArtifactLink, per learn.microsoft.com/en-us/azure/devops/boards/queries/link-type-reference (opens in new window):

def link_pull_request(work_item_id, org_name, project_id, repo_id, pr_id):
    # vstfs artifact URI format for a Git PR:
    # vstfs:///Git/PullRequestId/{projectId}/{repoId}/{prId}
    artifact_url = (
        f"vstfs:///Git/PullRequestId/{project_id}/{repo_id}/{pr_id}"
    )
    body = [{
        "op": "add",
        "path": "/relations/-",
        "value": {
            "rel": "ArtifactLink",
            "url": artifact_url,
            "attributes": {
                "name": "Pull Request",
                "comment": f"Fixing PR !{pr_id}",
            },
        }
    }]
    r = requests.patch(
        f"{BASE}/{PROJECT}/_apis/wit/workitems/{work_item_id}?api-version=7.1",
        json=body, headers=HEADERS,
    )
    r.raise_for_status()

Use az boards work-item relation list-type to enumerate all supported link type names for the current organisation, per learn.microsoft.com/en-us/azure/devops/boards/backlogs/add-link (opens in new window).

Bulk close after release

resolved = wiql_search(
    "SELECT [System.Id] FROM WorkItems "
    "WHERE [System.WorkItemType] = 'Bug' "
    "AND [System.State] = 'Resolved' "
    "AND [System.IterationPath] UNDER 'MyProject\\\\Sprint 42'"
)
for item in resolved:
    set_state(item["id"], "Closed")

az boards CLI

The az boards CLI (part of azure-devops Azure CLI extension) wraps the same REST API. Install with az extension add --name azure-devops.

# Create a Bug
az boards work-item create \
  --type Bug \
  --title "Login fails on SSO redirect" \
  --priority 2 \
  --org "$ADO_ORG" \
  --project "$ADO_PROJECT"

# Update state
az boards work-item update --id 4210 --state Active \
  --org "$ADO_ORG" --project "$ADO_PROJECT"

# Link two work items
az boards work-item relation add --id 4210 \
  --relation-type Related --target-id 4205 \
  --org "$ADO_ORG"

# WIQL query (returns JSON)
az boards query \
  --wiql "SELECT [System.Id],[System.Title] FROM WorkItems \
    WHERE [System.WorkItemType]='Bug' AND [System.State]='New'" \
  --org "$ADO_ORG" --project "$ADO_PROJECT"

Parsing results

create_bug returns (id, url). Build the browser permalink:

permalink = f"{BASE}/{PROJECT}/_workitems/edit/{work_item_id}"

wiql_search returns [{id, url}, ...]. Always check the list length before accessing index 0, and compare against the $top cap to detect truncation.

CI integration

# azure-pipelines.yml (excerpt)
- task: PythonScript@0
  displayName: "File ADO bug on test failure"
  condition: failed()
  inputs:
    scriptSource: filePath
    scriptPath: scripts/file-ado-bug.py
  env:
    ADO_ORG: $(System.CollectionUri)
    ADO_PROJECT: $(System.TeamProject)
    ADO_PAT: $(ADO_PAT_SECRET)
    BUILD_ID: $(Build.BuildId)
    BUILD_URL: $(System.CollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)

file-ado-bug.py reads the JUnit XML produced by the test runner, extracts the first failure, deduplicates against open Bugs, and calls create_or_comment.

Related skills

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.

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.