Skip to content

Skip triage for already-closed Jira issues - #661

Merged
opohorel merged 4 commits into
packit:mainfrom
opohorel:triage_closed_check
Jul 9, 2026
Merged

opohorel merged 4 commits into
packit:mainfrom
opohorel:triage_closed_check

Conversation

@opohorel

@opohorel opohorel commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Problem

When the triage queue is long, a Jira issue can be closed externally before the triage agent picks it up. The agent has no status check and burns LLM tokens triaging a resolved issue.

Solution

Add a deterministic pre-check in process_task that skips already-closed issues before any LLM work, by piggybacking on the existing get_jira_details API call (zero additional Jira round-trips).

Changes

  • tasks.py: Replace get_jira_labels with get_jira_issue_metadata that extracts both labels and status from the same get_jira_details response. Returns ([], None) on failure so triage proceeds safely.
  • triage_agent.py: After the existing label dedup check, return early if issue status is Closed or Done. For user-triggered runs (ymir_todo), remove the label and post a comment explaining the skip. Both actions respect dry_run.
  • test_tasks.py: Tests for the new metadata helper covering status extraction and failure defaults.
  • test_triage_agent.py: Tests verifying process_task skips closed issues, cleans up ymir_todo on user-triggered runs, and proceeds normally for open issues.

opohorel added 3 commits July 8, 2026 14:27
get_jira_labels already calls get_jira_details via MCP but discards
the status field.  Replace it with get_jira_issue_metadata that
extracts both labels and status from the same API response so callers
that need status don't pay for a second round-trip.

Assisted-by: Claude (Cursor)
When the triage queue is long an issue can be closed before the agent
picks it up.  Check the issue status right after the existing label
dedup and return early for Closed/Done issues, avoiding wasted LLM
tokens.  For user-triggered runs (ymir_todo) the label is removed and
a comment is posted so the requester gets feedback.

Assisted-by: Claude (Cursor)
Cover the new metadata helper (labels+status extraction, failure
defaults) and the triage agent's process_task behavior: closed/done
issues are skipped, user-triggered runs get ymir_todo cleanup and an
ack comment, open issues proceed normally.

Assisted-by: Claude (Cursor)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a check to skip triage for Jira issues that are already closed or done, updating the API calls to fetch both labels and status in a single request. Feedback suggests improving the cleanup logic on closed or done issues by unconditionally removing both ymir_todo and ymir_triage_in_progress labels if present, rather than only removing ymir_todo when the run is user-triggered.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread ymir/agents/triage_agent.py
@qodo-for-packit

Copy link
Copy Markdown

PR Summary by Qodo

Skip triage for already-closed Jira issues

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Fetch Jira labels and status together to avoid extra Jira round-trips.
• Skip triage early for issues already in Closed/Done to save LLM tokens.
• Add unit tests covering metadata extraction and closed-issue skip behavior.
Diagram

graph TD
  A["Task queue (Redis)"] --> B["triage_agent.process_task"] --> C["tasks.get_jira_issue_metadata"] --> D["Jira (get_jira_details)"] --> E{"Status Closed/Done?"}
  E -->|"Yes"| F["Skip triage"]
  F --> G["(user-triggered) remove ymir_todo + comment"]
  E -->|"No"| H["run_workflow (LLM triage)"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Filter/evict closed issues at enqueue time
  • ➕ Avoids queue churn by never enqueuing already-closed issues
  • ➕ Reduces work for all consumers, not just the triage agent
  • ➖ Doesn't cover issues that close after enqueue but before processing (still needs a processing-time guard)
  • ➖ Requires changes in the producer/labeling pipeline and potentially Jira automation
2. Add a dedicated status lookup call before triage
  • ➕ Keeps label lookup logic unchanged
  • ➕ Can be isolated to triage_agent without touching tasks helpers
  • ➖ Adds an extra Jira round-trip per task (cost + latency)
  • ➖ More moving parts for error handling and retries
3. Persist Jira status in task metadata and reuse across retries
  • ➕ Reduces repeated Jira reads on task retry loops
  • ➕ Makes behavior deterministic across reprocess attempts
  • ➖ Status can become stale quickly; still needs periodic refresh or validation
  • ➖ More state management complexity and migration considerations

Recommendation: The chosen approach (piggybacking status extraction onto the existing get_jira_details call) is the best tradeoff: it introduces a deterministic, low-risk guardrail that prevents wasted LLM work without increasing Jira traffic. Enqueue-time filtering can be a complementary optimization later, but it cannot replace the processing-time check because issues can close while waiting in the queue.

Files changed (4) +189 / -7

Enhancement (1) +7 / -4
tasks.pyReplace label-only Jira lookup with labels+status metadata helper +7/-4

Replace label-only Jira lookup with labels+status metadata helper

• Renames the helper from get_jira_labels to get_jira_issue_metadata and extracts both labels and status name from the same get_jira_details response. Updates failure behavior to return ([], None) and logs a metadata-specific warning, allowing callers to proceed safely when Jira/MCP lookup fails.

ymir/agents/tasks.py

Bug fix (1) +21 / -1
triage_agent.pyEarly-return in process_task for Closed/Done Jira issues (with user-triggered cleanup) +21/-1

Early-return in process_task for Closed/Done Jira issues (with user-triggered cleanup)

• Switches to using get_jira_issue_metadata and adds a deterministic status gate: if the issue is Closed or Done, triage is skipped before any workflow/LLM work. For user-triggered runs, it removes the ymir_todo label and posts an acknowledgement comment, both respecting dry_run.

ymir/agents/triage_agent.py

Tests (2) +161 / -2
test_tasks.pyAdd unit tests for get_jira_issue_metadata (status extraction + failure defaults) +51/-1

Add unit tests for get_jira_issue_metadata (status extraction + failure defaults)

• Introduces parametrized tests validating that labels and status are extracted correctly from a single Jira details payload. Adds a failure-path test asserting the helper returns empty labels and None status on MCP/tool exceptions.

ymir/agents/tests/unit/test_tasks.py

test_triage_agent.pyAdd process_task tests for closed-issue skipping and user-triggered cleanup +110/-1

Add process_task tests for closed-issue skipping and user-triggered cleanup

• Adds helpers to capture the process_task closure registered by main() in queue mode, then tests that Closed/Done issues skip run_workflow. Verifies that user-triggered closed issues remove ymir_todo and post an ack comment (respecting DRY_RUN), and that open issues still invoke run_workflow.

ymir/agents/tests/unit/test_triage_agent.py

@qodo-for-packit

qodo-for-packit Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 7 rules

Grey Divider


Action required

1. Stale in-progress label 🐞 Bug ≡ Correctness
Description
process_task skips Closed/Done issues but only removes ymir_todo for user-triggered runs,
leaving ymir_triage_in_progress behind and making the issue appear permanently in-progress. This
blocks future maintainer re-triggers because the fetcher ignores ymir_todo when an in-progress
label exists and treats any ymir_* labels as an "already processed" dedup anchor.
Code

ymir/agents/triage_agent.py[R892-909]

+            if current_status in (IssueStatus.CLOSED.value, IssueStatus.DONE.value):
+                logger.info(f"Skipping triage for {input.issue} — issue is already {current_status}")
+                if user_triggered:
+                    await tasks.set_jira_labels(
+                        jira_issue=input.issue,
+                        labels_to_remove=["ymir_todo"],
+                        dry_run=dry_run,
+                        user_triggered=True,
+                    )
+                    await tasks.post_user_ack_once(
+                        task,
+                        input.issue,
+                        "triage",
+                        f"Issue is already **{current_status}** — skipping triage.",
+                        user_triggered=True,
+                        dry_run=dry_run,
+                    )
+                return
Relevance

⭐⭐⭐ High

Team treats ymir_* labels as dedup anchors; past PRs require removing TRIAGE_IN_PROGRESS on all
terminal paths.

PR-#540
PR-#403
PR-#459

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The fetcher consumes ymir_todo by flipping it to ymir_triage_in_progress before enqueue, and it
will not honor ymir_todo as a trigger when an in-progress label exists; therefore removing only
ymir_todo in the closed-skip path leaves the dedup anchor stuck and blocks future re-triggers. The
normal triage path explicitly removes ymir_triage_in_progress when writing terminal labels, but
the new early return bypasses that cleanup.

ymir/agents/triage_agent.py[892-909]
ymir/common/constants.py[172-175]
ymir/jira_issue_fetcher/jira_issue_fetcher.py[562-588]
ymir/jira_issue_fetcher/jira_issue_fetcher.py[471-474]
ymir/agents/triage_agent.py[1034-1045]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
When skipping triage for Jira issues whose status is `Closed`/`Done`, the code removes `ymir_todo` but does not remove `ymir_triage_in_progress`. For user-triggered runs, `ymir_todo` is typically already consumed by the fetcher (flipped to `ymir_triage_in_progress`), so this leaves the issue stuck in an in-progress state and prevents re-triggering.

## Issue Context
- The Jira fetcher consumes `ymir_todo` by flipping it to `ymir_triage_in_progress` before enqueue.
- The fetcher will not treat `ymir_todo` as a valid trigger if an in-progress label is present.
- Normal triage completion removes `ymir_triage_in_progress`, but this new early-return bypasses that cleanup.

## Fix Focus Areas
- Update the Closed/Done skip path to remove `JiraLabels.TRIAGE_IN_PROGRESS.value` (and optionally also `JiraLabels.TODO.value` for DRY_RUN/fetcher-not-flipping scenarios).
- Update/extend unit tests to reflect actual fetcher behavior (user-triggered tasks typically have `ymir_triage_in_progress`, not `ymir_todo`).

### Suggested implementation sketch
- In the `if current_status in ...` block:
 - For `user_triggered`, call `set_jira_labels(..., labels_to_remove=[JiraLabels.TRIAGE_IN_PROGRESS.value, JiraLabels.TODO.value], ...)`.
 - Consider removing any other `ymir_*_in_progress`-style anchors relevant to triage, but at least triage’s in-progress label.

## Fix Focus Areas (code pointers)
- ymir/agents/triage_agent.py[892-909]
- ymir/agents/tests/unit/test_triage_agent.py[119-144]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Closed issues may requeue 🐞 Bug ☼ Reliability
Description
For non-user-triggered runs, the Closed/Done early-return writes no ymir_* label, so a fetcher
query that still matches closed issues (the repo’s DEFAULT_QUERY does) can re-enqueue the same
closed ticket on every sweep. This trades LLM token burn for repeated queue churn and Jira reads on
the same closed issues.
Code

ymir/agents/triage_agent.py[R892-910]

+            if current_status in (IssueStatus.CLOSED.value, IssueStatus.DONE.value):
+                logger.info(f"Skipping triage for {input.issue} — issue is already {current_status}")
+                if user_triggered:
+                    await tasks.set_jira_labels(
+                        jira_issue=input.issue,
+                        labels_to_remove=["ymir_todo"],
+                        dry_run=dry_run,
+                        user_triggered=True,
+                    )
+                    await tasks.post_user_ack_once(
+                        task,
+                        input.issue,
+                        "triage",
+                        f"Issue is already **{current_status}** — skipping triage.",
+                        user_triggered=True,
+                        dry_run=dry_run,
+                    )
+                return
+
Relevance

⭐⭐⭐ High

Historical reviews emphasize writing a terminal ymir_* label so fetcher sweeps skip
already-processed issues; avoids requeue churn.

PR-#540
PR-#459
PR-#527

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The fetcher’s default JQL doesn’t exclude closed tickets, and the fetcher only suppresses re-enqueue
when ymir_* labels exist; since the new closed-skip path returns without writing any ymir_*
label for non-user-triggered runs, closed issues can be repeatedly re-enqueued and re-checked each
sweep in such configurations.

ymir/agents/triage_agent.py[892-910]
ymir/jira_issue_fetcher/jira_issue_fetcher.py[55-56]
ymir/jira_issue_fetcher/jira_issue_fetcher.py[459-519]
ymir/agents/triage_agent.py[1036-1044]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
When `process_task` detects a Closed/Done Jira issue, it returns without writing any terminal/dedup label for non-user-triggered tasks. If the Jira fetcher query includes closed issues, the fetcher can keep re-enqueueing the same issue repeatedly because it only suppresses issues that already have `ymir_*` labels.

## Issue Context
- The Jira fetcher’s DEFAULT_QUERY has no status filter.
- The fetcher marks issues as "existing" (skip enqueue) when it sees any `ymir_*` labels (except `ymir_todo`/`ymir_retry_needed` special cases).
- Normal triage writes a terminal label specifically to dedupe future sweeps.

## Fix Focus Areas
Choose one of the following (or both):
1) **Fetcher-side**: Update the fetcher query default (and/or documented recommended QUERY) to exclude Done/Closed statuses.
2) **Triage-side**: On closed/done skip (at least for non-user-triggered), write an explicit dedup marker so the fetcher will skip the issue on future sweeps.
  - If no existing label is semantically appropriate, consider introducing a dedicated label like `ymir_triage_skipped_closed`.
  - If you reuse an existing label, ensure it won’t mislead downstream automation.

## Fix Focus Areas (code pointers)
- ymir/agents/triage_agent.py[892-910]
- ymir/jira_issue_fetcher/jira_issue_fetcher.py[55-56]
- ymir/jira_issue_fetcher/jira_issue_fetcher.py[459-519]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread ymir/agents/triage_agent.py
Comment thread ymir/agents/triage_agent.py
lbarcziova
lbarcziova previously approved these changes Jul 9, 2026

@lbarcziova lbarcziova left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread ymir/agents/triage_agent.py Outdated
Co-authored-by: Laura Barcziová <49026743+lbarcziova@users.noreply.github.com>
@opohorel
opohorel merged commit 850b51c into packit:main Jul 9, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants