Qa Review Action
Review QA feedback on a Jira ticket: classify each point as a genuine bug, intended behaviour, or out of scope — then propose fix…
Review QA feedback on a Jira ticket: classify each point as a genuine bug, intended behaviour, or out of scope — then propose fixes or push-back responses accordingly.
**Ticket:** $ARGUMENTS
---
## Step 0 — Normalise the ticket ID
If `$ARGUMENTS` is a full Jira URL (e.g. `https://*.atlassian.net/browse/$JIRA_PROJECT_KEY-42`), extract the ticket key from the URL path. If it is a bare number (e.g. `42`), prepend `$JIRA_PROJECT_KEY-`. Store the result as `TICKET_ID` and use it in place of `$ARGUMENTS` for all curl commands below.
## Step 1 — Verify credentials
Check that `JIRA_BASE_URL`, `JIRA_API_TOKEN`, and `JIRA_EMAIL` are set in `.env`. If any are missing, stop and ask the user to add them. Source `.env` before all Jira API calls.
## Step 2 — Fetch the ticket
```bash
source .env && curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
"$JIRA_BASE_URL/rest/api/3/issue/$TICKET_ID?expand=subtasks,renderedFields,attachment"
Extract:
fields.summaryrenderedFields.description(rendered HTML — this is the acceptance criteria / spec;fields.descriptionis ADF JSON and not human-readable)fields.status.namefields.issuetype.namefields.parent(if present — story or epic for broader context)fields.attachment(array of all attachments — id, filename, mimeType, content URL)
Step 3 — Fetch all comments
source .env && curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
"$JIRA_BASE_URL/rest/api/3/issue/$TICKET_ID/comment?startAt=0&maxResults=100&orderBy=created"
Check the total field in the response. If total exceeds the number of comments returned, continue fetching with startAt=100, startAt=200, etc. until all pages are retrieved. Concatenate all results into a single comment list.
For each comment, extract author.displayName, created, and convert the body field (which is Atlassian Document Format JSON) to plain text by extracting all text nodes while preserving whitespace and line breaks. Mark comments ending with _Actioned by Claude Code_ as Claude-authored; all others are human-authored.
Discard all Claude-authored comments. Only apply the QA feedback heuristics below to human-authored comments.
Filter human-authored comments to those that look like QA feedback. Signs of QA feedback:
- The author is not a developer (e.g. QA, tester, product, or similar role)
- The comment refers to behaviour observed in a deployed environment, screenshots, or specific reproduction steps
- The comment uses QA language: "bug", "expected", "actual", "screenshot", "reproduce", "regression", "UAT", "testing", "issue found", "not working", "incorrect", "should be"
If no comments look like QA feedback, tell the user: "No QA feedback comments found on TICKET_ID. Nothing to review." and stop.
Step 4 — Download and analyse screenshots
For each QA comment, check whether it references screenshots. Screenshots may appear as:
- Inline attachments embedded in the comment body using ADF media nodes (extract the
attrs.id— this is the attachment ID) - References by filename to entries in
fields.attachment
For every screenshot identified, download it to a temp path:
source .env && curl -s -L -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
-o /tmp/qa-screenshot-<id>.<ext> \
"$JIRA_BASE_URL/rest/api/3/attachment/content/<attachment-id>"
Then use the Read tool on each downloaded file to view it visually. Note what the screenshot shows: the page/section, the visible defect or unexpected behaviour, any error messages, and any relevant UI state.
Step 5 — Extract and number QA points
From the QA comments and screenshots, extract a flat numbered list of distinct issues raised. Each issue should be one clearly scoped observation, e.g.:
- "The chart legend is missing on the Rankings Overview section"
- "The export button throws a 500 error when clicked with no data"
- "The institution name truncates too aggressively on mobile"
If a single comment raises multiple distinct problems, split them into separate numbered points. Do not merge separate observations into one item.
Step 6 — Classify each QA point
For each numbered QA point, make a classification based on everything read so far:
Classification rules
GENUINE BUG — the behaviour is clearly unintended AND:
- The original ticket acceptance criteria imply this should work correctly, OR
- There is no ADR or documented decision that justifies the behaviour, AND
- It is not clearly outside the stated scope of the ticket
INTENDED BEHAVIOUR — an ADR or documented decision explicitly or implicitly describes why the system behaves this way. Quote the specific ADR and section. Do not classify as intended behaviour without a concrete reference.
OUT OF SCOPE — the QA point describes valid functionality, but it was never in the acceptance criteria of this ticket (or any linked ticket). This is a new request, not a defect. These should be logged as new tickets, not fixed here.
UNCERTAIN — none of the above applies with confidence. Use this only when the evidence is genuinely ambiguous after reading all available context. Document exactly why you are uncertain — what evidence points each way.
Step 7 — For GENUINE BUG points: analyse the codebase
For each point classified as GENUINE BUG, find the most likely source of the problem in the codebase:
- Read
apps/web/for frontend issues,apps/api/for backend issues. Usegrepandfindto locate relevant files — do not guess file paths. - Identify the specific file(s) and line(s) where the defect most likely originates.
- Propose a concrete fix: what to change, in which file, and why that change addresses the root cause.
- Note any tests that would need to be added or updated.
Be precise. Vague suggestions like "check the component" are not acceptable — name the file, the function, and the change.
Step 8 — Present findings and ask for decisions
Output a structured report. For each QA point:
[N]
Maintain Qa Review Action?
Let people know it's listed here — add the badge (live metrics, light/dark aware) or a plain link to your README or docs.
[Qa Review Action on getagentictools](https://getagentictools.com/loops/gavingreenwood-then-post-the-matching-transition-id?ref=badge) npx agentictools info loops/gavingreenwood-then-post-the-matching-transition-id The second line is the CLI lookup for this page — handy in READMEs and docs.