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.
76
96%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
A test failure produces structured data (XML, JSON, HTML) with everything a triager needs - assertion, stack, test name, environment. This workflow ingests that record and emits a ready-to-file bug spec.
It composes:
bug-lifecycle-reference
for initial state (New).severity-vs-priority-reference
for severity proposal (heuristic; reviewer confirms).defect-taxonomy-istqb
for type / root-cause hypothesis fields.jira-bug-workflow-runner,
linear-bug-workflow-runner,
github-issues-bug-workflow
as the filing backend.Distinct from screen-recording-driven bug reporting: this one starts from a CI failure record.
The skill accepts these inputs (auto-detected by extension):
| Format | Source | Schema reference |
|---|---|---|
| JUnit XML | pytest, JUnit, surefire, Playwright | <testsuites>/<testsuite>/<testcase>/<failure> per the de-facto schema (Apache Ant) at llg.cubic.org/docs/junit/ |
| Allure JSON | Allure framework (any language) | per-test JSON in allure-results/; schema at docs.qameta.io/allure-report |
pytest --tb=short log | pytest stdout/stderr | line-oriented; regex-driven |
| Playwright HTML report | Playwright trace | report.json inside the HTML bundle |
| TestNG XML | TestNG | similar to JUnit; per testng.org |
Parser bodies and per-format field shapes (JUnit, Allure, pytest,
Playwright, TestNG) live in references/parsers.md.
parse_junit(path) returns one dict per failing testcase; Allure's
first-class severity / feature / suite labels are harvested when present.
For each failure, propose values for the bug report:
| Field | Source | Default if unknown |
|---|---|---|
| Title | First line of failure.message truncated to 100 chars | "Test failure: {test_name}" |
| Body | Markdown with test name, stack, env, links | (always present) |
| Severity | Allure severity label OR inferred from assertion class (AssertionError → Medium; TimeoutError → High; ConnectionError → High) | Medium |
| Priority | Match severity by default; production-runner = bump | Medium |
| Defect type (IEEE 1044) | Inferred from stack location: tests/* → Test specification; app/* → Code (implementation) | Code |
| Component | Allure feature / suite label OR top-of-stack module | (none) |
Severity inference rules (heuristic, reviewer confirms):
SEVERITY_FROM_ERROR = {
"AssertionError": "medium",
"TimeoutError": "high",
"ConnectionError": "high",
"OutOfMemoryError": "critical",
"SecurityException": "critical",
}
def infer_severity(failure_type, message):
if failure_type in SEVERITY_FROM_ERROR:
return SEVERITY_FROM_ERROR[failure_type]
if "production" in message.lower() or "p0" in message.lower():
return "high"
return "medium"render_body(failure, env) emits a standard Markdown block consumed
verbatim by every platform runner - test name, assertion, stack,
environment, artefact links, a proposed-classification table (flagged
"triager to confirm"), reproduction steps, and dupe history. Full
template: references/spec-template.md.
Before filing, search the platform tracker for open bugs with matching title / test name. Use the per-platform skill:
# Pseudo
def find_dupes(platform, test_name):
if platform == "jira":
return jira_bug_workflow_runner.search_jql(
f'project = ENG AND text ~ "{test_name}" AND issuetype = Bug'
)
if platform == "linear":
return linear_bug_workflow_runner.find_dupes(TEAM_ID, test_name)
if platform == "github":
return github_issues_bug_workflow.search_issues(
f'is:open label:bug "{test_name}" in:title,body'
)If duplicates exist, the workflow attaches a comment instead of creating a new bug.
Emit a tracker-agnostic spec:
bug_spec:
title: "Test failure: checkout fails with promo X"
body: |
## Test failure
...
severity: high
priority: p2
labels: [bug, type:regression, component:checkout]
defect_type: Code
component: checkout
reproduction:
commit: "abc123"
command: "pytest tests/checkout/test_promo.py::test_stacked"
environment:
branch: main
ci_run: "https://github.com/.../runs/123"Then pass to the relevant platform-runner. Sample dispatcher:
def file_bug(spec, platform):
if platform == "jira":
return jira_bug_workflow_runner.create_bug(
project_key="ENG",
summary=spec["title"],
description_text=spec["body"],
severity=spec["severity"].capitalize(),
priority=spec["priority"].upper(),
labels=spec["labels"],
)
if platform == "linear":
return linear_bug_workflow_runner.create_bug(
team_id=os.environ["LINEAR_TEAM_ID"],
title=spec["title"],
description_md=spec["body"],
priority=PRIORITY_MAP[spec["priority"]],
state_id=BACKLOG_STATE_ID,
label_ids=resolve_label_ids(spec["labels"]),
)
if platform == "github":
return github_issues_bug_workflow.create_bug(
title=spec["title"],
body=spec["body"],
severity=spec["severity"],
priority=spec["priority"],
labels=spec["labels"],
)After filing:
from pathlib import Path
failures = parse_junit(Path("results.xml"))
for f in failures:
spec = {
"title": f"Test failure: {f['test'].split('::')[-1]}",
"body": render_body(f, env=collect_env_vars()),
"severity": infer_severity(f["type"], f["message"]),
"priority": "p3", # default; reviewer adjusts
"labels": ["bug", "auto-filed", "ci-failure"],
"defect_type": "Code",
"component": guess_component(f["test"]),
}
if find_dupes("github", f["test"]):
# Comment on the existing issue
continue
issue = file_bug(spec, "github")
print(f"Filed #{issue['number']}: {issue['html_url']}")| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Hand-copying assertion to bug | Stack truncation, escaping errors | Always parse the structured artefact |
| One bug per test failure ignoring deduplication | Tracker fills with the same flake | Always run Step 4 (dedupe) |
| Inferring severity from test name | "test_critical_path_*" doesn't mean failure is critical | Infer from assertion class + message keywords |
| No reproduction section | Triager can't repro; bug bounces back | Always include Step 3's commit + command |
| File before deduplication | Same defect filed N times in N CI runs | Search first |
| No artefacts linked | Triager can't see what happened | Always link screenshots / videos / HAR |
| Inferred classification not flagged as a proposal | Triager assumes it's confirmed; bad data downstream | Always label classification fields as "proposed - triager confirms" |