Skip to content

Rebase consolidation improvements and code review fixes - #726

Open
majamassarini wants to merge 67 commits into
packit:mainfrom
majamassarini:group-rebases
Open

Rebase consolidation improvements and code review fixes#726
majamassarini wants to merge 67 commits into
packit:mainfrom
majamassarini:group-rebases

Conversation

@majamassarini

Copy link
Copy Markdown
Member

Summary

This PR implements rebase consolidation (grouping multiple CVE issues into a single rebase MR) and addresses code review findings for performance and maintainability.

Key Features

Rebase Consolidation

  • Group sibling Jira issues requiring the same package rebase into a single MR
  • Link consolidated siblings to primary issue during triage and on failures
  • Prevent circular consolidation by excluding already-triaged issues
  • Handle "already at target version" scenarios gracefully

Code Review Fixes

  1. Semantic version comparison - Use rpmdev-vercmp instead of string equality for sibling version matching
  2. Parallelization - Use asyncio.gather() for sibling analysis and Jira API calls
  3. Eliminate duplication - Extract shared utilities for JQL building and label updates

Changes

Consolidation Implementation

  • ymir/agents/rebase_consolidation.py: Find and analyze sibling issues for consolidation
  • ymir/agents/triage_agent.py: Link siblings during triage, handle NOT_AFFECTED for already-rebased packages
  • ymir/agents/rebase_agent.py: Post comments/labels to all consolidated issues, helper for failure notifications
  • ymir/agents/prompts/triage/prompt.j2: Check current version before deciding REBASE resolution

Performance & Maintainability

  • Parallelization: Sibling analysis and Jira updates now run concurrently
  • Shared utilities: build_siblings_jql() for JQL construction, update_labels_for_all_issues() for label updates
  • Semantic comparison: compare_versions() in version_utils.py wraps rpmdev-vercmp

Testing

Tested against dotnet8.0 CVE issues (RHEL-211859 and 12 siblings):

  • Consolidated into single rebase MR
  • All siblings labeled and linked correctly
  • "Already at version" detection works (returns NOT_AFFECTED with link to existing rebase)

Related Issues

Addresses feedback from rebase consolidation implementation review and Slack discussion about RHEL-211859 "already at version" error.

🤖 Generated with Claude Code

@qodo-for-packit

Copy link
Copy Markdown

PR Summary by Qodo

Consolidate rebase MRs across sibling Jira issues + version-aware triage fixes

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Consolidate sibling CVE Jira issues into a single rebase MR when target versions match.
• Propagate MR links, Jira comments, and labels across all consolidated issues (success/failure).
• Improve triage/rebase prompting to detect “already at target version” and avoid redundant rebases.
Diagram

graph TD
  A["Triage agent"] --> B["Rebase consolidation"] --> C["RebaseData (consolidated list)"] --> D["Rebase agent"] --> E["MR creation"] --> F["Jira updates"]
  B --> G["Version utils (rpmdev-vercmp)"]
  subgraph Legend
    direction LR
    _a["Agent/workflow"] ~~~ _m["Model"] ~~~ _u["Utility"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Deterministic sibling matching (no LLM)
  • ➕ More predictable behavior and lower runtime cost
  • ➕ Easier to test and reason about matching rules
  • ➖ Harder to reliably extract target versions across varied Jira text/comments
  • ➖ May reduce consolidation rate vs. LLM-based interpretation
2. Persist consolidation via Jira links/fields instead of labels+comments
  • ➕ Richer traceability in Jira (relationship is queryable)
  • ➕ Less reliance on comment text conventions
  • ➖ Requires Jira schema/permission changes and more complex writes
  • ➖ May not be available in all projects/environments
3. Centralize fan-out side effects in a shared helper module
  • ➕ Avoids duplicating parallel comment/label patterns across agents
  • ➕ Makes future consolidation types cheaper to add
  • ➖ Refactor scope increases and may slow down delivery of current feature

Recommendation: The chosen approach (LLM verification + explicit eligibility gating + RebaseData propagation) is a good fit for heterogeneous Jira content and matches the existing rebuild consolidation pattern. If runtime/cost becomes an issue, consider a hybrid: deterministic pre-filtering (e.g., summary regex) followed by LLM only for ambiguous candidates.

Files changed (10) +591 / -40

Enhancement (5) +491 / -24
rebase_agent.pyFan out MR/Jira updates to consolidated rebase siblings +114/-23

Fan out MR/Jira updates to consolidated rebase siblings

• Extends the rebase workflow state to carry consolidated issues and a consolidation summary. MR descriptions now include links to all related Jira issues, and Jira comments/labels are applied across the group (parallelized with asyncio.gather). Failure handling posts detailed errors to the primary issue and link-style notifications to siblings.

ymir/agents/rebase_agent.py

rebase_consolidation.pyNew module to discover and validate rebase sibling issues +258/-0

New module to discover and validate rebase sibling issues

• Introduces a rebase consolidation workflow: build JQL to find sibling candidates, gate on CVE triage eligibility, and use an LLM to confirm the sibling requires the exact same rebase target. Uses rpmdev-vercmp-backed comparison to avoid string-equality pitfalls and analyzes candidates concurrently.

ymir/agents/rebase_consolidation.py

triage_agent.pyIntegrate rebase consolidation step into triage workflow +67/-1

Integrate rebase consolidation step into triage workflow

• Adds a consolidate_rebase_siblings workflow step and routes REBASE resolutions through it (including after applicability checks). On successful triage, consolidated siblings are labeled to prevent re-triage and receive link comments pointing to the primary issue.

ymir/agents/triage_agent.py

models.pyExtend RebaseData with consolidation metadata and helper property +13/-0

Extend RebaseData with consolidation metadata and helper property

• Adds consolidated_issues and consolidation_summary fields to RebaseData, plus an all_jira_issues helper for primary + siblings. This enables downstream agents to treat grouped rebases as a first-class model concern.

ymir/common/models.py

version_utils.pyAdd rpmdev-vercmp wrapper for semantic version comparison +39/-0

Add rpmdev-vercmp wrapper for semantic version comparison

• Introduces compare_versions() to compare upstream versions using rpmdev-vercmp exit codes, raising clear errors when the tool is missing or fails. This is used by consolidation logic to avoid incorrect string-based matching.

ymir/common/version_utils.py

Bug fix (2) +23 / -1
instructions.j2Treat “already at target version” as a graceful rebase outcome +4/-1

Treat “already at target version” as a graceful rebase outcome

• Updates rebase instructions to avoid hard errors when the package is already at (or newer than) the target version. The prompt now asks the agent to return a failure with actionable guidance (search for existing builds and attach to Errata).

ymir/agents/prompts/rebase/instructions.j2

prompt.j2Add mandatory “already at target version” check before choosing REBASE +19/-0

Add mandatory “already at target version” check before choosing REBASE

• Extends triage decision guidance to require checking the dist-git spec version and comparing via rpmdev-vercmp before deciding on a rebase. If already rebased, the agent is instructed to return NOT_AFFECTED and reference the existing rebase/build when possible.

ymir/agents/prompts/triage/prompt.j2

Refactor (1) +12 / -15
rebuild_consolidation.pyReuse shared sibling JQL builder for rebuild consolidation +12/-15

Reuse shared sibling JQL builder for rebuild consolidation

• Refactors rebuild sibling discovery to call the new shared build_siblings_jql helper and reuse centralized excluded-label lists. Removes duplicated fixVersion-variant handling from this module.

ymir/agents/rebuild_consolidation.py

Tests (2) +65 / -0
test_rebase_consolidation.pyAdd unit tests for rebase sibling JQL construction +34/-0

Add unit tests for rebase sibling JQL construction

• Adds coverage for fixVersion variants, component escaping, and excluded-label behavior in build_rebase_siblings_jql. Ensures consolidation queries don’t accidentally exclude/include incorrect triage states.

ymir/agents/tests/unit/test_rebase_consolidation.py

test_models.pyTest RebaseData.all_jira_issues behavior +31/-0

Test RebaseData.all_jira_issues behavior

• Adds unit tests validating all_jira_issues returns only the primary key when no consolidation exists, and includes sibling keys when provided. Confirms expected ordering and non-destructive behavior of base fields.

ymir/common/tests/unit/test_models.py

@qodo-for-packit

qodo-for-packit Bot commented Jul 31, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Sibling queued without marker ✓ Resolved 🐞 Bug ≡ Correctness ⭐ New
Description
queue_siblings_for_triage() logs and continues when posting the “Queued for triage as potential
sibling of <primary>” comment fails, but later stages rely on that comment to (a) recognize a
finishing issue as a sibling and (b) extract the primary issue key. This can strand the primary
indefinitely in WAITING_FOR_SIBLINGS (because the sibling label may never be removed and the primary
is never re-queued) and can also let the sibling proceed down the normal REBASE path as its own
primary.
Code

ymir/agents/rebase_consolidation.py[R429-430]

+                except Exception as e:
+                    logger.warning(f"Failed to comment on sibling {candidate_key}: {e}")
Relevance

●●● Strong

Team historically accepts workflow-stalling robustness fixes; marker failure can strand issues, so
likely addressed.

PR-#540
PR-#611

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The sibling enqueue path explicitly continues after a marker comment failure, while both sibling
detection in triage and primary extraction in the readiness check depend on that marker comment
being present.

ymir/agents/rebase_consolidation.py[404-444]
ymir/agents/triage_agent.py[1365-1382]
ymir/agents/rebase_consolidation.py[507-532]

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

## Issue description
Sibling correlation depends on a specific Jira comment marker, but `queue_siblings_for_triage()` swallows failures from `tasks.comment_in_jira(...)` and still enqueues the sibling task. Downstream logic then cannot reliably detect sibling status or identify the primary issue.

## Issue Context
- `triage_agent` sets `is_sibling` by scanning comments for the marker.
- `check_and_queue_primary_if_ready()` extracts the primary key from that same marker comment.
- If the marker comment was never written, the sibling may not remove `ymir_rebase_sibling` and the primary may never be re-queued.

## Fix Focus Areas
- ymir/agents/rebase_consolidation.py[404-444]
- ymir/agents/triage_agent.py[1365-1382]
- ymir/agents/rebase_consolidation.py[507-532]

## Suggested change
- Treat failure to post the marker comment as a hard failure for queueing that sibling:
 1) If `comment_in_jira` fails, roll back the sibling label you just added (best-effort),
 2) Do **not** enqueue the sibling task to Redis,
 3) Do **not** increment `queued_count`.
- Alternatively (more robust), include the `primary_issue` in the Redis task metadata so the sibling can notify the primary without relying on Jira comments, but that’s a broader change.

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


2. Unsafe sibling label removal ✓ Resolved 🐞 Bug ☼ Reliability ⭐ New
Description
In triage_agent, when is_sibling is inferred from a marker comment, the code unconditionally
executes current_labels.remove(JiraLabels.REBASE_SIBLING.value) on a pre-triage label snapshot; if
the label isn’t present this raises ValueError and can abort the post-triage flow (including
downstream dispatch and primary-ready checks). This can happen whenever the marker comment exists
but the label is missing from current_labels (e.g., label removed externally or never present),
causing the worker to fail after already writing terminal labels.
Code

ymir/agents/triage_agent.py[R1403-1405]

+                    # Update current_labels to reflect the changes we just made
+                    if is_sibling:
+                        current_labels.remove(JiraLabels.REBASE_SIBLING.value)
Relevance

●●● Strong

Deterministic crash risk (ValueError on missing label); repo tends to accept defensive guards for
Jira field/label handling.

PR-#729
PR-#743

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code determines is_sibling by comment text, but then mutates current_labels (captured
earlier) with an unconditional remove(), which will throw if the label is absent from that
snapshot.

ymir/agents/triage_agent.py[1199-1210]
ymir/agents/triage_agent.py[1365-1406]

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

## Issue description
`current_labels.remove(JiraLabels.REBASE_SIBLING.value)` can raise `ValueError` because `current_labels` is a pre-workflow snapshot and sibling-ness is determined via comment lookup. This exception can crash the triage worker after it has already written terminal Jira labels.

## Issue Context
- `current_labels` comes from `tasks.get_jira_issue_metadata(...)` before the workflow runs.
- `is_sibling` is set by scanning Jira comments for the marker text.

## Fix Focus Areas
- ymir/agents/triage_agent.py[1199-1210]
- ymir/agents/triage_agent.py[1365-1406]

## Suggested change
- Replace `list.remove(...)` with a guarded removal:
 - `if JiraLabels.REBASE_SIBLING.value in current_labels: current_labels.remove(...)`, or
 - convert to a set and use `discard()`.
- (Optional but safer) If later logic depends on `current_labels`, refresh labels after `set_jira_labels(...)` instead of mutating a potentially stale snapshot.

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


3. Primary requeue skipped ✓ Resolved 🐞 Bug ≡ Correctness
Description
check_and_queue_primary_if_ready re-queues the primary into TRIAGE_QUEUE via Task.from_issue(), but
triage_agent skips non-user-triggered tasks when terminal ymir_* labels (e.g., ymir_triaged_rebase)
already exist. Since the primary already has ymir_triaged_rebase from Phase 1, the re-queued primary
is dropped and never reaches the rebase queue.
Code

ymir/agents/rebase_consolidation.py[R548-553]

+            # Triage will see the existing ymir_triaged_rebase label, skip expensive analysis,
+            # and queue for rebase with proper full state (Task.metadata = state.model_dump())
+            task = Task.from_issue(primary_issue, user_triggered=user_triggered)
+            async with redis_client(os.environ["REDIS_URL"]) as redis:
+                await fix_await(redis.lpush(RedisQueues.TRIAGE_QUEUE.value, task.model_dump_json()))
+            logger.info(f"Re-queued {primary_issue} to triage (will queue to rebase with full state)")
Relevance

●●● Strong

Matches prior accepted feedback: terminal-label skip can drop re-queued tasks; workflow would stall.

PR-#540

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The re-queued task is non-user-triggered and the Jira issue already has a terminal triage label, so
triage_agent will hit the early-return skip path and do nothing with the task.

ymir/agents/rebase_consolidation.py[547-553]
ymir/common/models.py[99-104]
ymir/agents/triage_agent.py[1187-1209]

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

## Issue description
The resume mechanism re-queues the primary into the triage queue, but triage dedup logic returns early when terminal labels exist and the task is not user-triggered. This prevents the primary from ever being dispatched to the rebase queue in normal (automated) runs.

## Issue Context
- `Task.from_issue()` contains only the Jira key (no prior triage state).
- `_process_triage_locked` skips processing when terminal labels exist and `not user_triggered`.

## Fix Focus Areas
- ymir/agents/rebase_consolidation.py[547-553]
- ymir/agents/triage_agent.py[1189-1209]
- ymir/common/models.py[99-104]

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


View action required (10)
4. Wrong Jira details parsing ✓ Resolved 🐞 Bug ≡ Correctness
Description
rebase_consolidation reads labels/comments from top-level keys (e.g., primary_details.get("labels"),
sibling_details.get("comments")), but get_jira_details returns these under details["fields"] (labels
in fields["labels"], comments in fields["comment"]["comments"]). This prevents extracting the
primary issue from sibling comments and prevents Phase 3 sibling comment matching, breaking
consolidation/release logic.
Code

ymir/agents/rebase_consolidation.py[R468-471]

+        comments = sibling_details.get("comments", [])
+        for comment in comments:
+            body = comment.get("body", "")
+            if "Queued for triage as potential sibling of" in body:
Relevance

●●● Strong

Deterministic data-shape mismatch; team often accepts fixes to Jira field parsing/defensive access.

PR-#729

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Jira tool returns labels and comments inside the fields object; the new consolidation code
looks for them at the top level, so it will always see empty lists and fail to match/advance the
workflow.

ymir/agents/rebase_consolidation.py[466-478]
ymir/agents/rebase_consolidation.py[487-505]
ymir/agents/rebase_consolidation.py[632-639]
ymir/tools/privileged/jira.py[172-176]
ymir/tools/privileged/tests/unit/test_jira.py[60-83]

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

## Issue description
`get_jira_details` returns a dict with `fields` containing `labels` and `comment`, but the consolidation code reads `labels`/`comments` from top-level keys. As a result, sibling comments are never scanned and the primary waiting label is never detected.

## Issue Context
- `GetJiraDetailsTool._postprocess_issue_data` returns `{key, id, fields}` (no top-level `labels`/`comments`).
- Comments are nested at `fields["comment"]["comments"]`.

## Fix Focus Areas
- ymir/agents/rebase_consolidation.py[466-477]
- ymir/agents/rebase_consolidation.py[487-505]
- ymir/agents/rebase_consolidation.py[632-640]

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


5. Primary task missing state ✓ Resolved 🐞 Bug ≡ Correctness
Description
check_and_queue_primary_if_ready() enqueues a Redis Task whose metadata only contains
jira_issue/target_branch, but rebase_agent queue processing requires the full serialized triage
state (triage_result.data, target_branch, etc.); this will fail when the primary is queued after
siblings finish.
Code

ymir/agents/rebase_consolidation.py[R555-558]

+                    task_metadata = {
+                        "jira_issue": primary_issue,
+                        "target_branch": target_branch,
+                    }
Relevance

●●● Strong

Queue consumers expect full state in Task.metadata; past changes use state.model_dump() for
downstream queues.

PR-#589

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new code enqueues a Task with only jira_issue/target_branch, while the rebase agent queue
consumer treats task.metadata as a full triage_state and immediately indexes triage_result.data,
making the payload incompatible and causing runtime failure when this queueing path is exercised.

ymir/agents/rebase_consolidation.py[529-563]
ymir/agents/rebase_agent.py[630-637]

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

## Issue description
`check_and_queue_primary_if_ready()` is pushing an incompatible `Task.metadata` shape onto the rebase queue (only `jira_issue` and `target_branch`). The rebase worker expects `task.metadata` to be the serialized triage state (including `triage_result.data`), so this path will KeyError/abort and the primary will not be processed.

## Issue Context
- Normal triage->rebase dispatch uses `Task(metadata=state.model_dump(), ...)`.
- The new sibling-completion path must enqueue the same payload shape, not a minimal dict.

## Fix Focus Areas
- ymir/agents/rebase_consolidation.py[529-563]
- ymir/agents/rebase_agent.py[630-637]

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


6. Sibling queue count unreliable ✓ Resolved 🐞 Bug ☼ Reliability
Description
queue_siblings_for_triage() increments queued_count only after comment_in_jira() succeeds, but
comment_in_jira can raise; if it fails after the sibling is already enqueued to Redis, sibling_count
can incorrectly return 0 and triage_agent will proceed as if there are no siblings, violating the
wait-for-siblings requirement.
Code

ymir/agents/rebase_consolidation.py[R376-379]

+                await tasks.comment_in_jira(
+                    jira_issue=candidate_key,
+                    agent_type="Triage",
+                    comment_text=f"Queued for triage as potential sibling of {primary_issue}",
Relevance

●●● Strong

Team favors isolating Jira failures; count/flow shouldn’t depend on comment success (best-effort
writes precedent).

PR-#611
PR-#540

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
In the sibling queue loop, Redis enqueue happens before the Jira comment, and queued_count
increments only after the comment call. Because comment_in_jira does not catch exceptions, a comment
failure can cause queued_count to undercount even though the sibling was already enqueued, and
triage_agent uses that count to decide whether the primary should wait.

ymir/agents/rebase_consolidation.py[361-388]
ymir/agents/tasks.py[382-403]
ymir/agents/triage_agent.py[961-989]

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

## Issue description
`queue_siblings_for_triage()` performs Redis enqueue + Jira label/comment side effects in a single try-block, but only increments `queued_count` after `comment_in_jira()`. Since `comment_in_jira()` can raise, you can end up with a sibling enqueued in Redis but not counted, leading the primary path to believe no siblings were queued and to continue without waiting.

## Issue Context
`triage_agent.consolidate_rebase_siblings()` uses `sibling_count > 0` to decide whether to set `state.waiting_for_siblings` (which controls downstream rebase queue dispatch). A miscount here breaks the consolidation contract.

## Fix Focus Areas
- ymir/agents/rebase_consolidation.py[361-388]
- ymir/agents/triage_agent.py[961-989]
- ymir/agents/tasks.py[382-403]

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


7. Dry-run bypasses Jira/Redis ✓ Resolved 🐞 Bug ☼ Reliability
Description
queue_siblings_for_triage() and check_and_queue_primary_if_ready() perform Redis queueing and Jira
label/comment mutations with dry_run=False and user_triggered=False hardcoded, so DRY_RUN triage
executions can still create real side effects and lose user-triggered behavior.
Code

ymir/agents/rebase_consolidation.py[R358-361]

+            await tasks.set_jira_labels(
+                jira_issue=candidate_key,
+                labels_to_add=[JiraLabels.REBASE_SIBLING.value],
+                dry_run=False,
Relevance

●●● Strong

Dry-run should not perform Redis/Jira writes; team often accepts reliability fixes in triage/retry
flows.

PR-#540
PR-#611

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The consolidation helper hardcodes dry_run=False and performs Redis LPUSH; triage calls it prior
to the step that ends early on dry_run, so these side effects can happen even when DRY_RUN is
enabled.

ymir/agents/rebase_consolidation.py[352-373]
ymir/agents/rebase_consolidation.py[503-537]
ymir/agents/triage_agent.py[955-1000]

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

### Issue description
The new consolidation helpers enqueue tasks and mutate Jira labels/comments without honoring `dry_run` (and they also hardcode `user_triggered=False`). In DRY_RUN mode, triage currently still calls `queue_siblings_for_triage()` before it exits, so a dry run can push tasks into Redis and modify Jira.

### Issue Context
DRY_RUN is expected to be non-mutating; this can trigger unexpected real triage/rebase work and alter Jira state.

### Fix Focus Areas
- ymir/agents/rebase_consolidation.py[287-414]
- ymir/agents/rebase_consolidation.py[503-537]
- ymir/agents/triage_agent.py[955-1000]

### Suggested fix
1) Add `dry_run: bool` and `user_triggered: bool` parameters to `queue_siblings_for_triage()` and `check_and_queue_primary_if_ready()`.
2) Gate *all* side effects (Redis LPUSH, Jira label updates, Jira comments) on `not dry_run`.
3) Pass the correct `user_triggered` value through so priority queue selection and notification gating remain consistent.

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


8. is_sibling may be unbound ✓ Resolved 🐞 Bug ☼ Reliability
Description
In triage_agent.retry(), is_sibling is assigned only when output.resolution != Resolution.ERROR,
but it is referenced afterward unconditionally; when the workflow returns Resolution.ERROR this
raises UnboundLocalError and prevents the intended retry/error handling.
Code

ymir/agents/triage_agent.py[R1333-1336]

+                # If this was a sibling issue, check if primary is ready to be queued
+                if is_sibling:
+                    logger.info(f"Sibling {input.issue} finished triaging, checking if primary is ready")
+                    try:
Relevance

●●● Strong

Potential UnboundLocalError crash in retry path; similar robustness issues have been accepted
before.

PR-#540
PR-#589

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The variable is defined only in the non-ERROR branch but referenced afterward, so the ERROR
resolution path can crash deterministically with UnboundLocalError.

ymir/agents/triage_agent.py[1247-1270]
ymir/agents/triage_agent.py[1333-1343]

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

### Issue description
`is_sibling` is defined inside the `if resolution_label and output.resolution != Resolution.ERROR:` block, but later code runs `if is_sibling:` outside that block. If `output.resolution == Resolution.ERROR`, the assignment is skipped and Python raises `UnboundLocalError`, breaking triage error processing.

### Issue Context
This can mask real triage failures and prevents retry()/dispatch from running.

### Fix Focus Areas
- ymir/agents/triage_agent.py[1247-1270]
- ymir/agents/triage_agent.py[1333-1343]

### Suggested fix
Set `is_sibling = False` before the resolution-label block, or move the “check/queue primary” logic inside a guarded branch that only runs when `is_sibling` has been computed (and/or when resolution is not ERROR).

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


9. Invalid JQL when empty ✓ Resolved 🐞 Bug ≡ Correctness
Description
build_siblings_jql() always emits labels not in (...); when find_triaged_rebase_siblings() passes
excluded_labels=[], this becomes labels not in () which is invalid JQL, causing Phase 3 sibling
lookup to fail and return no consolidated siblings.
Code

ymir/agents/rebase_consolidation.py[R564-568]

+        jql = build_siblings_jql(
+            issue_key=jira_issue,
+            component=rebase_data.package,
+            fix_version=rebase_data.fix_version,
+            excluded_labels=[],  # Don't exclude any labels
Relevance

●●● Strong

Deterministic invalid JQL with empty list; likely treated as a real correctness bug to fix.

PR-#555

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Phase 3 search explicitly passes an empty exclusion list, while the shared JQL builder
unconditionally renders a labels not in (...) clause; with an empty list this becomes `labels not
in ()`, which is invalid and will make Jira search throw (then be swallowed by the try/except,
yielding an empty sibling set).

ymir/agents/rebase_consolidation.py[33-66]
ymir/agents/rebase_consolidation.py[544-582]

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

### Issue description
`find_triaged_rebase_siblings()` calls `build_siblings_jql(..., excluded_labels=[])`, but `build_siblings_jql()` always appends `AND labels not in ({excluded})`. With an empty list this produces `labels not in ()` which Jira rejects, so the search fails (caught) and consolidation silently returns no siblings.

### Issue Context
This breaks the new “Phase 3” consolidation path that relies on finding `ymir_triaged_rebase` siblings via JQL.

### Fix Focus Areas
- ymir/agents/rebase_consolidation.py[33-66]
- ymir/agents/rebase_consolidation.py[544-579]

### Suggested fix
In `build_siblings_jql()`, only include the `labels not in (...)` clause when `excluded_labels` is non-empty (otherwise omit that clause entirely). Optionally add a unit test for `build_siblings_jql(..., excluded_labels=[])` to ensure valid JQL is produced.

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


10. Sibling failure comments skipped ✓ Resolved 🐞 Bug ≡ Correctness
Description
post_failure_comments_to_consolidated_siblings() calls `tasks.comment_in_jira(...,
is_error=True), which is suppressed when user_triggered=False`, so consolidated siblings won’t
receive failure/link comments during normal automated runs. This undermines the “link siblings on
failures” behavior (siblings only get failure context when runs are ymir_todo-triggered).
Code

ymir/agents/rebase_agent.py[R218-225]

+                await tasks.comment_in_jira(
+                    jira_issue=consolidated.issue_key,
+                    agent_type="Rebase",
+                    comment_text=f"Consolidated rebase failed. See {primary_issue} for error details.",
+                    available_tools=available_tools,
+                    is_error=True,
+                    user_triggered=user_triggered,
+                )
Relevance

●●● Strong

Deterministic logic bug: is_error comments suppressed for non-user-triggered runs; breaks intended
sibling linking.

PR-#611

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The helper marks sibling link comments as is_error=True, but tasks.comment_in_jira returns early
on error comments when user_triggered is false; this means the helper’s intended side-effect does
not occur in typical automated runs.

ymir/agents/rebase_agent.py[204-229]
ymir/agents/tasks.py[382-395]
ymir/agents/rebase_agent.py[472-509]

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

## Issue description
Consolidated sibling failure-link comments are posted via `tasks.comment_in_jira` with `is_error=True`, but `tasks.comment_in_jira` intentionally skips error comments unless `user_triggered=True`. As a result, on non-user-triggered (automatic) runs, consolidated sibling issues never get the failure/link comment.

## Issue Context
This helper is intended to post an informational link comment to consolidated siblings pointing to the primary issue for details, even when we suppress noisy error notifications.

## Fix Focus Areas
- ymir/agents/rebase_agent.py[216-229]

## Suggested fix
Change the sibling link comment to be non-error (e.g., `is_error=False`) so it is always posted, while keeping detailed error comments to the primary issue gated by `is_error=True`/`user_triggered` as today. If you still need special formatting for failures, consider adding a dedicated “informational failure link” helper that bypasses the error-suppression rule.

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


11. Comment fanout can crash ✓ Resolved 🐞 Bug ☼ Reliability
Description
The rebase workflow now posts Jira comments to multiple issues via asyncio.gather() without
per-issue error handling; a single Jira API failure will raise and abort the workflow. This can
trigger retries after side effects (e.g., MR already opened) and leave some consolidated issues
without status updates.
Code

ymir/agents/rebase_agent.py[R440-454]

+                    # Post same success message to all issues in parallel
+                    all_issues = [state.jira_issue] + [item.issue_key for item in state.consolidated_issues]
+                    await asyncio.gather(
+                        *[
+                            tasks.comment_in_jira(
+                                jira_issue=issue,
+                                agent_type="Rebase",
+                                comment_text=comment_text,
+                                is_error=is_error,
+                                available_tools=gateway_tools,
+                                user_triggered=user_triggered,
+                            )
+                            for issue in all_issues
+                        ]
+                    )
Relevance

●●● Strong

Team previously added per-issue try/except for Jira comment fanout to avoid aborting on one failure.

PR-#611

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
rebase_agent now uses asyncio.gather() to fan out Jira comments to multiple issues, while
tasks.comment_in_jira does not handle exceptions and will propagate failures from
run_tool(add_jira_comment). The queue-mode runner treats uncaught exceptions as retryable
failures, so comment failures can cause retries after other steps already succeeded.

ymir/agents/rebase_agent.py[432-471]
ymir/agents/tasks.py[382-403]
ymir/agents/rebase_agent.py[615-645]

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

## Issue description
`ymir/agents/rebase_agent.py` posts comments to `[primary] + consolidated` issues using `asyncio.gather(...)` on `tasks.comment_in_jira(...)`. Because `tasks.comment_in_jira()` does not catch exceptions (it directly awaits `run_tool('add_jira_comment', ...)`), any single Jira comment failure will cause `gather()` to raise and the workflow step to abort.

This is especially risky because the comment step is late in the workflow (after rebase/build/MR creation). A transient Jira failure while commenting can therefore convert an otherwise-successful run into an exception path and trigger retries.

## Issue Context
- Success path uses `asyncio.gather` for multiple comments.
- Failure path also posts to consolidated siblings without local try/except in `post_failure_comments_to_consolidated_siblings`.
- `tasks.comment_in_jira` propagates tool exceptions.

## Fix Focus Areas
- ymir/agents/rebase_agent.py[432-472]
- ymir/agents/tasks.py[382-403]
- ymir/agents/rebase_agent.py[615-645]

## What to change
- Wrap each `tasks.comment_in_jira(...)` call so one failure doesn’t abort the entire step.
 - Option A: `results = await asyncio.gather(*coros, return_exceptions=True)` and log exceptions per issue.
 - Option B: loop with per-issue `try/except` (still can be parallelized with bounded concurrency).
- Apply the same per-issue error isolation to `post_failure_comments_to_consolidated_siblings()` so a single sibling comment failure doesn’t crash the workflow.

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


12. JQL/test contradiction ✓ Resolved 🐞 Bug ≡ Correctness
Description
build_rebase_siblings_jql() excludes ymir_triaged_rebase, but the newly-added unit tests assert
that ymir_triaged_rebase must NOT appear in the JQL, which makes the tests fail and leaves
intended consolidation behavior ambiguous. This will block CI and/or ship incorrect sibling
filtering depending on which behavior is intended.
Code

ymir/agents/rebase_consolidation.py[R75-81]

+        excluded_labels=[
+            JiraLabels.TRIAGED_NOT_AFFECTED.value,
+            JiraLabels.TRIAGED_BACKPORT.value,
+            JiraLabels.TRIAGED_REBUILD.value,
+            JiraLabels.TRIAGED_REBASE.value,
+            JiraLabels.TRIAGED_POSTPONED.value,
+        ],
Relevance

●●● Strong

Deterministic test/implementation mismatch; similar consolidation JQL behavior was adjusted before
to stop excluding triaged labels.

PR-#554

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The implementation explicitly excludes TRIAGED_REBASE, while the new tests explicitly assert it
must not be excluded; since TRIAGED_REBASE equals ymir_triaged_rebase, these cannot both be
correct and will fail tests as written.

ymir/agents/rebase_consolidation.py[65-82]
ymir/agents/tests/unit/test_rebase_consolidation.py[4-34]
ymir/common/constants.py[137-165]

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

## Issue description
`build_rebase_siblings_jql()` currently adds `JiraLabels.TRIAGED_REBASE.value` to `excluded_labels`, which makes the produced JQL exclude issues already labeled `ymir_triaged_rebase`. However, the new unit tests assert that `"ymir_triaged_rebase"` is *not* present in the JQL.

This contradiction will deterministically fail the test suite and also indicates unclear desired behavior (should already-triaged-rebase issues be eligible for consolidation or excluded to avoid circular consolidation?).

## Issue Context
- `JiraLabels.TRIAGED_REBASE.value` is defined as `"ymir_triaged_rebase"`.
- Tests currently expect `"ymir_triaged_rebase"` to be absent from the JQL.

## Fix Focus Areas
- ymir/agents/rebase_consolidation.py[65-82]
- ymir/agents/tests/unit/test_rebase_consolidation.py[4-34]
- ymir/common/constants.py[137-165]

## What to change
Choose one and make code+tests consistent:
1) If preventing circular consolidation is the goal: keep excluding `TRIAGED_REBASE` and update the tests/docstrings to assert it *is* present in the `labels not in (...)` clause.
2) If triaged-rebase issues should still be considered siblings: remove `JiraLabels.TRIAGED_REBASE.value` from `excluded_labels` and keep the current tests.

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


13. Waiting label check is stale ✓ Resolved 🐞 Bug ≡ Correctness
Description
triage_agent.retry() decides whether to skip rebase queueing by checking WAITING_FOR_SIBLINGS in the
pre-triage current_labels snapshot, but the label is added later during
queue_siblings_for_triage(), so the primary can still be queued immediately even when siblings
were queued.
Code

ymir/agents/triage_agent.py[R1380-1383]

+                                # Skip queueing if issue is waiting for siblings to finish triaging
+                                if JiraLabels.WAITING_FOR_SIBLINGS.value in current_labels:
+                                    logger.info(
+                                        f"Issue {input.issue} is waiting for siblings to finish triaging, "
Relevance

●● Moderate

Logic/race on label snapshot vs later writes; plausible fix, but behavior expectations unclear
without precedent.

PR-#555
PR-#540

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
current_labels is captured once before triage starts, the waiting label is added later by the
consolidation helper, and the dispatch logic checks only the old snapshot—so it can’t reliably see
the waiting label that was just written.

ymir/agents/triage_agent.py[1086-1097]
ymir/agents/triage_agent.py[955-982]
ymir/agents/triage_agent.py[1377-1390]
ymir/agents/rebase_consolidation.py[386-410]

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

### Issue description
The new wait-for-siblings path relies on `ymir_waiting_for_siblings` to prevent the primary from being enqueued for rebase until siblings finish triaging. However, `current_labels` is read once at the start of `process_task()`, and the downstream dispatch later checks this stale snapshot. Since `queue_siblings_for_triage()` adds `ymir_waiting_for_siblings` mid-workflow, the dispatch check can miss it and enqueue the primary anyway.

### Issue Context
This violates the PR’s “primary must wait for siblings” constraint and can lead to the primary starting rebase before siblings complete triage.

### Fix Focus Areas
- ymir/agents/triage_agent.py[1086-1097]
- ymir/agents/triage_agent.py[955-982]
- ymir/agents/triage_agent.py[1377-1390]
- ymir/agents/rebase_consolidation.py[386-409]

### Suggested fix
Before deciding the downstream queue for `Resolution.REBASE`, refresh labels from Jira (or return an explicit flag from `consolidate_rebase_siblings` / `queue_siblings_for_triage` indicating the primary is waiting). Then use that refreshed/explicit state to skip queueing. Avoid relying on the initial `current_labels` snapshot for this decision.

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



Remediation recommended

14. Sibling tasks lose priority ✓ Resolved 🐞 Bug ☼ Reliability
Description
queue_siblings_for_triage() enqueues sibling triage tasks with Task.from_issue(candidate_key) and
always pushes them to TRIAGE_QUEUE, so a user-triggered (ymir_todo) primary run will still enqueue
siblings as non-user-triggered/normal-priority work. This can delay sibling triage completion and
suppress user-triggered behavior for the sibling task’s own workflow steps (retries, error handling,
notifications).
Code

ymir/agents/rebase_consolidation.py[R399-402]

+                # Queue for triage AFTER label AND comment
+                task = Task.from_issue(candidate_key)
+                async with redis_client(os.environ["REDIS_URL"]) as redis:
+                    await fix_await(redis.lpush(RedisQueues.TRIAGE_QUEUE.value, task.model_dump_json()))
Relevance

●●● Strong

Team previously accepted fixes preserving user_triggered in queue selection; sibling tasks should
inherit user_triggered/priority.

PR-#589

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Sibling tasks are created without passing user_triggered and are pushed to the non-priority triage
queue, even though Task.from_issue supports user_triggered and triage retry logic uses
task.user_triggered to select the appropriate queue.

ymir/agents/rebase_consolidation.py[399-403]
ymir/common/models.py[83-104]
ymir/agents/triage_agent.py[1267-1276]

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

### Issue description
Sibling triage tasks queued during rebase consolidation are created with `Task.from_issue(candidate_key)` and pushed to the normal triage queue unconditionally. This drops the `user_triggered` signal (and priority) from the primary run, causing sibling triage to behave like an automatic run.

### Issue Context
- `Task.from_issue(..., user_triggered=...)` exists and defaults to `False`.
- The triage worker listens to a priority twin queue (`TRIAGE_QUEUE_TODO`) for user-triggered tasks.

### Fix Focus Areas
- ymir/agents/rebase_consolidation.py[399-403]
- ymir/common/models.py[99-104]

### Suggested change
- Build the task as `Task.from_issue(candidate_key, user_triggered=user_triggered)`.
- Push to the correct queue:
 - `RedisQueues.TRIAGE_QUEUE_TODO.value` when `user_triggered` is true
 - else `RedisQueues.TRIAGE_QUEUE.value`

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


15. Missing target_version breaks compare ✓ Resolved 🐞 Bug ≡ Correctness
Description
find_rebase_siblings() calls compare_versions(analysis.target_version, ...) when
requires_same_rebase is true, but target_version is optional in the SiblingRebaseAnalysis schema. If
the LLM returns null for target_version, the comparison raises and that sibling is treated as an
analysis failure (excluded).
Code

ymir/agents/rebase_consolidation.py[R183-185]

+            if analysis.requires_same_rebase:
+                cmp_result = compare_versions(analysis.target_version, rebase_data.version)
+                if cmp_result == 0:
Relevance

●●● Strong

Null/optional schema fields causing runtime failures are typically guarded or made optional to avoid
crashes.

PR-#81

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The schema declares target_version as optional, but the compare call is unconditional inside the
requires_same_rebase branch.

ymir/agents/rebase_consolidation.py[85-95]
ymir/agents/rebase_consolidation.py[183-207]

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

### Issue description
`SiblingRebaseAnalysis.target_version` is nullable, but the code assumes it’s always present when `requires_same_rebase` is true and passes it into `compare_versions()`. A null value triggers an exception and causes an avoidable false-negative (sibling excluded).

### Issue Context
The exception is caught and the candidate is excluded, so the workflow won’t crash, but consolidation quality suffers and debugging is harder.

### Fix Focus Areas
- ymir/agents/rebase_consolidation.py[85-95]
- ymir/agents/rebase_consolidation.py[183-207]

### Suggested fix
- Add an explicit guard:
 - If `analysis.requires_same_rebase` and not `analysis.target_version`, return an exclusion summary like "missing target_version" without calling `compare_versions()`.
- Alternatively, enforce a Pydantic validator: `requires_same_rebase=True` => `target_version` must be non-null (and ideally non-empty).

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


16. Label fanout aborts on error ✓ Resolved 🐞 Bug ☼ Reliability
Description
In rebase_agent.update_labels_for_all_issues(), a single Jira label update failure will raise out of
asyncio.gather() and can interrupt the overall rebase processing while leaving some issues labeled
and others not. This affects success/failure/error paths that now label primary + consolidated
siblings together.
Code

ymir/agents/rebase_agent.py[R159-173]

+        """Update Jira labels for primary issue and all consolidated siblings in parallel."""
+        # Deduplicate in case consolidated_issues contains duplicates or the primary issue
+        all_issues = list(dict.fromkeys([primary_issue] + [item.issue_key for item in consolidated_issues]))
+        await asyncio.gather(
+            *[
+                tasks.set_jira_labels(
+                    jira_issue=issue,
+                    labels_to_add=labels_to_add,
+                    labels_to_remove=labels_to_remove,
+                    dry_run=dry_run,
+                    user_triggered=user_triggered,
+                )
+                for issue in all_issues
+            ]
+        )
Relevance

●●● Strong

Team has accepted per-issue Jira error isolation/dedup patterns; gather abort risk likely fixed
similarly.

PR-#611
PR-#427

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The helper fans out label writes via gather with no try/except/return_exceptions, and it is invoked
in the retry exhaustion path and in both success and failure labeling paths.

ymir/agents/rebase_agent.py[151-173]
ymir/agents/rebase_agent.py[601-667]
ymir/agents/rebase_agent.py[701-731]

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

### Issue description
`update_labels_for_all_issues()` uses `asyncio.gather()` without per-issue exception isolation. If `tasks.set_jira_labels()` raises for one issue, the whole await raises, which can disrupt the rebase task and leave inconsistent labels across the primary/sibling issues.

### Issue Context
This helper is used from multiple rebase paths (final retry exhaustion, success, and failure), so it needs to be resilient to partial Jira outages.

### Fix Focus Areas
- ymir/agents/rebase_agent.py[151-173]
- ymir/agents/rebase_agent.py[618-626]
- ymir/agents/rebase_agent.py[703-714]
- ymir/agents/rebase_agent.py[723-731]

### Suggested fix
- Wrap each `tasks.set_jira_labels(...)` call in a small inner coroutine with `try/except` (like the comment fanout helper), log warnings per issue, and continue.
 - OR use `asyncio.gather(..., return_exceptions=True)` and iterate results to log failures.
- Ensure the helper never raises due to a single sibling label failure (unless you explicitly want to fail the whole workflow).

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


View review recommended (3)
17. Comment fanout not deduped ✓ Resolved 🐞 Bug ☼ Reliability
Description
post_comments_to_all_issues() fans out comments to [primary] + siblings without deduplicating
issue keys, so duplicate entries in consolidated_issues will post multiple identical comments to
the same Jira issue. This can create redundant notifications and noisy issue histories.
Code

ymir/agents/rebase_agent.py[R187-202]

+        all_issues = [primary_issue] + [item.issue_key for item in consolidated_issues]
+
+        async def post_with_error_handling(issue: str) -> None:
+            try:
+                await tasks.comment_in_jira(
+                    jira_issue=issue,
+                    agent_type="Rebase",
+                    comment_text=comment_text,
+                    is_error=is_error,
+                    available_tools=available_tools,
+                    user_triggered=user_triggered,
+                )
+            except Exception as e:
+                logger.warning(f"Failed to post comment to {issue}: {e}")
+
+        await asyncio.gather(*[post_with_error_handling(issue) for issue in all_issues])
Relevance

●●● Strong

Accepted precedent: team deduped Jira comment fanout to avoid duplicate notifications/noise.

PR-#611

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new helper builds the fanout list via simple concatenation and schedules one comment per
element; no uniqueness filter is applied, so duplicates in the input will result in duplicate
comment attempts.

ymir/agents/rebase_agent.py[174-203]
PR-#611

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

## Issue description
The new parallel comment fanout constructs `all_issues` by concatenating the primary issue with sibling issue keys and then iterates it directly. If duplicates are present in `consolidated_issues`, the same Jira issue will receive multiple identical comments.

## Issue Context
Even if duplicates are not expected from the consolidation query, this function is a shared utility and should be robust against malformed/duplicated inputs.

## Fix Focus Areas
- ymir/agents/rebase_agent.py[187-202]

## Suggested fix
Deduplicate `all_issues` in an order-preserving way before calling `asyncio.gather`, e.g.:

```py
all_issues = list(dict.fromkeys([primary_issue] + [ci.issue_key for ci in consolidated_issues]))
```

Apply the same pattern anywhere else you build an issue fanout list for comments/labels if applicable.

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


18. Version compare blocks event loop ✓ Resolved 🐞 Bug ☼ Reliability
Description
compare_versions() uses synchronous subprocess.run() without a timeout, and it is called from async
sibling analysis; a slow or stuck rpmdev-vercmp call will block the event loop and stall
consolidation progress. This can manifest as long pauses during triage/rebase sibling analysis.
Code

ymir/common/version_utils.py[R35-41]

+    try:
+        result = subprocess.run(  # noqa: S603
+            ["rpmdev-vercmp", version1, version2],  # noqa: S607
+            capture_output=True,
+            text=True,
+            check=False,
+        )
Relevance

●● Moderate

Blocking subprocess in async is a concern, but switching to async subprocess/executor+timeout is a
larger behavior change.

PR-#526

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The comparator runs a blocki

[Comment truncated to fit github's 65,536-char limit.]

Comment thread ymir/agents/rebase_consolidation.py Outdated
Comment thread ymir/agents/rebase_agent.py Outdated
@majamassarini

Copy link
Copy Markdown
Member Author

/agentic_review

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

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit ec90fb4

@majamassarini

Copy link
Copy Markdown
Member Author

/agentic_review

Comment thread ymir/agents/rebase_agent.py Outdated
Comment thread ymir/agents/rebase_consolidation.py Outdated
Comment thread ymir/agents/rebase_consolidation.py Outdated
Comment thread ymir/common/version_utils.py
@qodo-for-packit

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit a2b67a7

@majamassarini

Copy link
Copy Markdown
Member Author

/agentic_review

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

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 634f48e

@majamassarini

Copy link
Copy Markdown
Member Author

/agentic_review

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

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit e19b4a0

@majamassarini
majamassarini force-pushed the group-rebases branch 3 times, most recently from ce969cb to cb92d04 Compare August 5, 2026 10:03
@majamassarini

Copy link
Copy Markdown
Member Author

/agentic_review

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

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6a219db

@majamassarini

Copy link
Copy Markdown
Member Author

/agentic_review

@qodo-for-packit

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 7753470

@majamassarini

Copy link
Copy Markdown
Member Author

/agentic_review

Comment thread ymir/agents/rebase_consolidation.py Outdated
Increment queued_count immediately after the Redis push (the critical
operation), not after comment_in_jira. This prevents miscount when
Jira label/comment operations fail but the sibling is already queued.

If comment_in_jira raised, we would skip the count increment, leading
consolidate_rebase_siblings to believe no siblings were queued and
continue without setting waiting_for_siblings=True.

Now label/comment are best-effort with individual try-except blocks,
and the count reflects actual Redis queue state.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
If an issue has ymir_rebase_sibling label, it's already part of
another primary's sibling group. Skip searching for more siblings
to avoid:
- Re-posting duplicate "Queued for triage as potential sibling"
  comments on the same siblings
- Treating siblings as new primary issues
- Creating confusing nested sibling relationships

Siblings should just proceed to rebase without consolidation logic.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
get_jira_details returns {key, id, fields}, not top-level labels/comments.
Labels are at fields.labels, comments at fields.comment.comments.

Fixed three locations:
- check_and_queue_primary_if_ready: sibling comment scan (line 468)
- check_and_queue_primary_if_ready: primary label check (line 492)
- find_triaged_rebase_siblings: sibling comment verification (line 634)

Without this fix, siblings never find their primary issue in comments,
and primaries are never detected as waiting for siblings.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Keep ymir_waiting_for_siblings label on the primary when re-queuing to
triage after siblings finish. This allows the primary to bypass the dedup
check (non-terminal label exception) and be processed.

Triage will automatically remove ymir_waiting_for_siblings along with
other ymir_* labels during normal processing (line 1285-1297).

Removing the label before re-queuing created a race where triage would
see only ymir_triaged_rebase (terminal), fail the dedup check, and skip
processing in automated runs.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
When a sibling is queued, there's a race between:
1. Sibling gets queued to triage
2. Sibling label gets added to Jira
3. Sibling starts triaging

If (3) happens before (2), the sibling won't see its own label and will
treat itself as a primary, queueing more siblings.

Now check both:
- ymir_rebase_sibling label (fast path)
- "Queued for triage as potential sibling" comment (fallback)

This prevents siblings from acting as primaries when label hasn't
propagated yet.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Before queueing a sibling, check if it has ymir_rebase_sibling or
ymir_triaged_rebase labels. This prevents:
- Multiple primaries from queuing the same sibling
- Duplicate comments on siblings
- Siblings being treated as part of multiple groups

Labels are more robust than comments for detecting whether a sibling
was already processed by another primary.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Queue sibling to triage AFTER setting its label, not before. This ensures
the sibling sees ymir_rebase_sibling when it starts processing and won't
treat itself as a primary.

Previous order:
1. Push to Redis queue
2. Add label (async)
3. Sibling starts triaging (label might not be visible yet)
4. Sibling treats itself as primary

New order:
1. Add label (blocks until confirmed)
2. Push to Redis queue
3. Sibling starts triaging (label is visible)
4. Sibling skips consolidation

If labeling fails, skip queueing the sibling to avoid it being treated
as a primary.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Order of operations for queueing a sibling:
1. Set ymir_rebase_sibling label (blocks until Jira confirms)
2. Post "Queued for triage as potential sibling" comment
3. Push to Redis triage queue

This maximizes the chance that when the sibling starts processing, it
will see EITHER the label OR the comment (or both) and skip consolidation.

Even if Jira has eventual consistency issues, both markers are written
before the sibling can start processing.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Siblings should not create their own MR. Only the primary issue
consolidates all siblings into a single MR.

Add check_if_sibling step at the start of rebase workflow:
- Check comments for "Queued for triage as potential sibling" marker
- If found, exit immediately without processing
- Log that the primary will handle consolidation

Cannot check ymir_rebase_sibling label because triage cleanup removes
all ymir_* labels before queueing to rebase.

Siblings already called check_and_queue_primary_if_ready() in triage
(line 1476), so no further action needed in rebase agent.

Without this check, siblings would create duplicate MRs for the same
rebase.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Siblings inherit the user_triggered flag from the primary issue to
preserve priority and signal intent.

Changes:
- Pass user_triggered to Task.from_issue(candidate_key, user_triggered)
- Queue to TRIAGE_QUEUE_TODO when user_triggered=True (priority queue)
- Queue to TRIAGE_QUEUE when user_triggered=False (normal queue)

Without this fix, siblings of user-triggered primaries would be treated
as automatic runs, losing priority and bypassing user-triggered-only
logic (like posting acknowledgement comments).

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
check_and_queue_primary_if_ready was only checking for siblings with
ymir_rebase_sibling label, but triage removes this label when it starts
processing (line 1308 in triage_agent). This caused primaries to be
queued too early when some siblings were still in triage.

Changes:
- Search for siblings with EITHER ymir_rebase_sibling OR ymir_triage_in_progress
- For in-progress siblings, verify they are siblings of THIS primary by checking comments
- Only queue primary when ALL actual siblings are done (no label OR completed)

Without this fix, RHEL-212117 was stuck with ymir_waiting_for_siblings
because it was queued before all siblings finished (siblings lost the
label when triage started but were still processing).

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
When rebase agent runs consolidation, it should include siblings that
were already triaged (have ymir_triaged_rebase label). This allows
re-running the primary to consolidate with siblings even after they
finish triage.

Changes:
- Add exclude_triaged parameter to build_rebase_siblings_jql (default True)
- find_rebase_siblings sets exclude_triaged=False to include all siblings
- Fetch comment field and verify siblings were queued by THIS primary
- Filter candidates to only those with sibling comment mentioning this primary
- queue_siblings_for_triage uses default (exclude_triaged=True) to only queue new siblings

Without this fix, re-running RHEL-212117 would find 0 siblings because
all siblings already have ymir_triaged_rebase label, causing the primary
to create a separate MR instead of consolidating.

The comment verification prevents consolidating siblings that belong to
a different primary with the same component/fix_version.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
When a sibling finishes triage, check_and_queue_primary_if_ready is only
called if is_sibling is True. The check was using current_labels which
was fetched at the start (line 1217), but ymir_rebase_sibling was already
removed from Jira by line 1308 cleanup. This caused siblings to NOT call
check_and_queue_primary_if_ready, leaving primaries stuck forever.

Changes:
- Check for sibling comment instead of ymir_rebase_sibling label
- Comment "Queued for triage as potential sibling of" is never removed
- This ensures siblings always call check_and_queue_primary_if_ready

Without this fix, RHEL-211884 finished triage but never checked if
RHEL-212117 (primary) was ready, leaving it stuck with
ymir_waiting_for_siblings.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
get_jira_details tool only accepts issue_key parameter, not fields.
The fields parameter was causing ToolInputValidationError.

Fixed in:
- triage_agent.py: 2 occurrences (consolidate_rebase_siblings, is_sibling check)
- rebase_agent.py: 1 occurrence (check_if_sibling)

Error was: Extra inputs are not permitted [type=extra_forbidden, input_value=['comment']]

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Critical bug: siblings were being queued for their own rebase after triage
completion, instead of only triggering check_and_queue_primary_if_ready().

Root cause:
- is_sibling check was performed (line 1469-1480) to call check_and_queue_primary_if_ready()
- BUT the downstream queueing logic (line 1515-1533) only checked if the issue
  is WAITING for siblings, not if it IS a sibling
- So siblings with Resolution.REBASE were queued normally

Result: Multiple rebases triggered (one per sibling), defeating consolidation.

Fix:
- Added is_sibling check BEFORE waiting_for_siblings check in rebase queueing
- Siblings now skip queueing entirely with log message:
  "Issue X is a sibling, skipping rebase queue (will be consolidated with primary)"

This ensures only the primary issue gets queued for rebase after all siblings
finish triaging.

Discovered via Phoenix/logs analysis:
- RHEL-234827 (sibling) completed triage with Resolution.REBASE
- Log showed: "Pushed RHEL-234827 to rebase_queue_c10s"
- is_sibling check ran but didn't prevent queueing

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Two related bugs:

1. Siblings with different resolutions block primary from queueing
   - Primary triages to REBASE
   - Sibling triages to BACKPORT/REBUILD/etc (different fix needed)
   - Primary waits forever for sibling to "finish"
   - But sibling IS finished, just with different resolution

2. Terminal label failures prevent consolidation from proceeding
   - Network error prevents setting ymir_triaged_* label
   - Sibling stays with ymir_triage_in_progress label
   - Primary blocked waiting for sibling (looks in-progress)

Fixes:

1. Exclude all terminal labels from pending sibling check:
   - ymir_triaged_rebase, ymir_triaged_backport, ymir_triaged_rebuild,
     ymir_triaged_not_affected, ymir_triaged_postponed
   - These siblings are done, don't block primary

2. Mark terminal label update as critical (enables retry on failure):
   - critical=True triggers 3 retry attempts with backoff
   - Raises exception if all retries fail
   - Prevents proceeding without terminal label set

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Bug: current_labels.remove(JiraLabels.REBASE_SIBLING.value) can raise
ValueError and crash the triage worker AFTER terminal labels have been
written to Jira.

Root cause:
- current_labels is fetched at line 1199 BEFORE workflow runs
- is_sibling is determined by comment check (line 1373-1381)
- ymir_rebase_sibling label was already removed at line 1308 (cleanup)
- current_labels snapshot is stale and doesn't have the label
- list.remove() raises ValueError if item not present

Scenario:
1. Sibling starts triage
2. Line 1308: ymir_rebase_sibling label removed from Jira
3. Line 1373-1381: is_sibling=True (found via comment)
4. Line 1394-1400: Terminal label written (critical=True, succeeds)
5. Line 1405: current_labels.remove(REBASE_SIBLING) → ValueError!
6. Worker crashes after successful label write

Fix:
Guard the removal with existence check:
  if is_sibling and JiraLabels.REBASE_SIBLING.value in current_labels:
      current_labels.remove(...)

This prevents ValueError while maintaining the intent to sync
current_labels with Jira state (used at line 1531 for waiting check).

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Critical bug: Siblings queued without marker comment cannot be detected
as siblings, breaking consolidation workflow.

Root cause:
queue_siblings_for_triage() has two safety writes before queueing:
1. Line 408-417: Add ymir_rebase_sibling label → has 'continue' on failure ✅
2. Line 421-431: Post marker comment → logs warning but continues ❌
3. Line 440: Sibling queued to Redis anyway

The marker comment is CRITICAL because downstream code depends on it:
- is_sibling check (line 1373-1381 in triage_agent.py) scans for this comment
- check_and_queue_primary_if_ready() (line 515-527) extracts primary issue from it

Without the comment:
- Sibling doesn't detect is_sibling=True
- Sibling triggers its own rebase (defeats consolidation!)
- check_and_queue_primary_if_ready() cannot find primary issue key
- Primary never gets queued for rebase

Fix:
If comment_in_jira fails:
1. Roll back ymir_rebase_sibling label (best effort)
2. Do NOT queue sibling task to Redis (continue to next sibling)
3. Do NOT increment queued_count

This ensures siblings are only queued when BOTH safety writes succeed,
maintaining the invariant that queued siblings can always be detected
and correlated back to their primary.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
The MCP get_jira_details tool returns comment bodies in Jira's ADF
(Atlassian Document Format), not as plain text. The is_sibling check was
looking for comment.get("body", "") as a string, but "body" is actually a
nested JSON structure:
{
  "type": "doc",
  "content": [
    {"type": "paragraph", "content": [{"type": "text", "text": "..."}]}
  ]
}

This caused is_sibling to always return False, so siblings were being queued
for rebase instead of being skipped.

Added _extract_text_from_adf() helper to recursively extract text from ADF
nodes, and updated the is_sibling check to use it.

Related issue: RHEL-234827 (sibling) triggered rebase workflow after being
queued as sibling of RHEL-234905.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
The MCP get_jira_details tool returns comment bodies in Jira's ADF
(Atlassian Document Format), not as plain text. Both the is_sibling check
in triage_agent.py and the primary issue extraction in
check_and_queue_primary_if_ready were looking for comment.get("body", "")
as a string, but "body" is actually a nested JSON structure.

This caused:
1. is_sibling to always return False → siblings queued for rebase
2. check_and_queue_primary_if_ready to fail with "No primary issue found
   in comments" → primary never queued even when all siblings done

Added extract_text_from_adf() to ymir.common.utils to recursively extract
text from ADF nodes, and updated both code paths to use it.

Fixes: RHEL-234827 (sibling) triggered rebase workflow
Fixes: RHEL-234905 (primary) not queued when all siblings finished

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
The sibling marker comment contains an inlineCard node with the primary
issue key in its URL:
  {"type": "text", "text": "Queued for triage as potential sibling of "},
  {"type": "inlineCard", "attrs": {"url": "https://.../RHEL-234905#..."}}

The previous extract_text_from_adf only extracted from "text" nodes and
"content" arrays, so it returned:
  "Queued for triage as potential sibling of "
with no issue key.

This caused check_and_queue_primary_if_ready to fail with "No primary
issue found in comments" because the regex couldn't find RHEL-\d+ in the
extracted text.

Updated extract_text_from_adf to also extract URLs from inlineCard nodes,
so the regex can find the issue key in the URL.

Fixes: Primary issue not queued when all siblings finished triaging

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
The pending siblings check at line 624 was still using the old
comment.get("body", "") without extract_text_from_adf().

This caused the check to fail when verifying if a ymir_triage_in_progress
issue is actually a sibling of THIS primary (vs a sibling of some other
primary). The comment body is ADF JSON, not plain text, so the string
checks for "Queued for triage as potential sibling of" and primary_issue
always failed.

Result: pending siblings count was wrong, so check_and_queue_primary_if_ready
thought there were no pending siblings even when RHEL-223787 was still
in progress with ymir_rebase_sibling label.

Updated to use extract_text_from_adf() like the other comment checks.

Fixes: Primary not queued when actual pending siblings exist

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
The fetcher was skipping ANY issue with ANY ymir_* label (except
ymir_retry_needed and ymir_todo). This meant that siblings queued by
rebase consolidation with ymir_rebase_sibling label would be skipped
by the fetcher if they got lost from Redis (e.g. Redis restart, or
never successfully pushed).

The consolidation code queues siblings and adds the ymir_rebase_sibling
label as a marker, but if the Redis queue entry gets lost before the
triage agent processes it, the fetcher would never re-queue it because
it sees the ymir_* label and marks it as "existing".

Result: RHEL-223787 was queued on Aug 10 with ymir_rebase_sibling label
but never triaged, blocking the primary RHEL-234905 indefinitely.

Added ymir_rebase_sibling to the list of labels that don't prevent
fetcher from queueing (like ymir_retry_needed and ymir_todo).

Fixes: Siblings queued with ymir_rebase_sibling never get triaged
Fixes: Primary blocked indefinitely waiting for lost sibling

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
When all siblings finish triaging, check_and_queue_primary_if_ready
re-queues the primary to triage, but the ymir_waiting_for_siblings label
was never removed. This causes triage to skip queueing for rebase
(line 1540: queue = None) because it sees the label and thinks siblings
are still pending.

Result: If rebase fails or is interrupted, retriggering the primary will
skip queueing because the label is still there, even though all siblings
finished long ago. The issue is stuck forever.

Fixed by removing ymir_waiting_for_siblings in check_and_queue_primary_if_ready
BEFORE re-queueing the primary. This is the right place because we KNOW
for certain that all siblings are done at this point. Marked as critical=True
so it retries 3 times with backoff - if this fails, the primary is stuck
forever anyway.

Fixes: Primary stuck with ymir_waiting_for_siblings after rebase interrupted
Fixes: RHEL-234905 will be stuck if current rebase fails

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
When triage finds siblings for a rebase, it sets state.waiting_for_siblings=True
and adds ymir_waiting_for_siblings label. But it was also adding the terminal
label ymir_triaged_rebase at the same time.

When all siblings finish, check_and_queue_primary_if_ready re-queues the primary
to triage. But triage's dedup check sees ymir_triaged_rebase (terminal label)
and skips:

  "Skipping duplicate triage for RHEL-234905 — already has labels:
   ['ymir_triaged_rebase']"

Result: Primary never queued for rebase, stuck forever.

Fixed by skipping terminal label when state.waiting_for_siblings=True or
ymir_waiting_for_siblings label is present. The terminal label will be added
when the primary is re-triaged after all siblings finish.

Fixes: Primary not queued for rebase after all siblings finish
Fixes: RHEL-234905 stuck with both ymir_triaged_rebase + ymir_waiting_for_siblings

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…ncies

Issue 1: Already-triaged siblings queued again
RHEL-223919 had ymir_backported (already finished backport with MR), but
was queued as sibling of RHEL-234905 because the check only looked for
ymir_rebase_sibling and ymir_triaged_rebase, missing other terminal labels.

Fixed by checking ALL terminal labels before queueing siblings:
- ymir_triaged_* (all resolutions)
- ymir_backported, ymir_rebased, ymir_rebuilt

Issue 2: Circular sibling dependencies
RHEL-234375 was queued as sibling of RHEL-234905, then triaged and found
its own siblings, becoming a primary waiting for siblings. Then it was
queued AGAIN as sibling of RHEL-234905, creating circular dependency.

Root cause: The sibling check at line 970 used comment.get("body", "")
which doesn't work with ADF, so it never detected that RHEL-234375 was
a sibling and allowed it to queue siblings.

Fixed by using extract_text_from_adf() in the sibling check so siblings
correctly skip consolidation and don't search for their own siblings.

Fixes: Already-completed issues re-triaged as siblings
Fixes: Circular sibling dependencies (sibling becomes primary)

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Three locations were still using comment.get("body", "") without ADF extraction:

1. rebase_agent.py line 290: check_if_sibling
   - Siblings not detected → create duplicate MRs instead of consolidating

2. rebase_consolidation.py line 178: find_rebase_siblings verified_candidates filter
   - All candidates filtered out as "not queued by this primary" → empty consolidation

3. rebase_consolidation.py line 759: find_triaged_rebase_siblings
   - Verified siblings excluded from consolidation → incomplete MRs

All now use extract_text_from_adf() to handle ADF-formatted comment bodies from MCP.

Fixes: Code review findings packit#1, packit#2, packit#7

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
If primary issue is closed/resolved but rebase workflow runs from stale
Redis message, find_triaged_rebase_siblings would still consolidate siblings
with the closed primary → MR description references invalid/closed issue →
broken links and confusion.

Added validation at start of find_triaged_rebase_siblings to check if primary
is Closed/Done/Resolved. If so, skip consolidation and log warning.

Example scenario prevented:
1. RHEL-500 (primary) + RHEL-400 (sibling) triaged
2. RHEL-500 manually closed
3. Old rebase workflow message processes
4. Would create MR linking RHEL-400 to closed RHEL-500 ✗
5. Now skips consolidation ✓

Fixes: Code review finding packit#8

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
When primary issue is waiting for siblings, the comment now lists which
siblings it's waiting for instead of just the count:

Before: "Waiting for 3 sibling(s) to finish triaging before starting rebase"

After:  "Waiting for 3 sibling(s) to finish triaging before starting rebase:
         RHEL-234827, RHEL-234375, RHEL-224663"

Jira automatically converts issue keys to clickable links, making it easy
to navigate to siblings and track consolidation progress.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
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