Skip to content

Fix: Skip buildroot check for Y-stream issues when dependency shipped from older Z-stream - #766

Closed
majamassarini wants to merge 5 commits into
packit:mainfrom
majamassarini:fix/ystream-skip-buildroot-check-for-shipped-zstream-deps
Closed

majamassarini wants to merge 5 commits into
packit:mainfrom
majamassarini:fix/ystream-skip-buildroot-check-for-shipped-zstream-deps

Conversation

@majamassarini

Copy link
Copy Markdown
Member

Summary

Fixes incorrect postponement of Y-stream rebuild issues when their dependency was already shipped from an older Z-stream.

Problem

When a Y-stream issue (e.g., rhel-10.3) depends on a package that was fixed in an older Z-stream (e.g., rhel-10.0.z) and is already Done-Errata, the triage agent would:

  1. ✅ Correctly reason that the dependency is shipped and flows from CentOS Stream
  2. ❌ But then check if the Z-stream build NVR exists in the Y-stream buildroot
  3. ❌ Find it's not there (different NVR: .el10_0 vs .el10)
  4. ❌ Incorrectly postpone the issue

Example: RHEL-189822 (go-fdo-client, rhel-10.3) waiting for RHEL-189749 (golang, rhel-10.0.z, Done-Errata with golang-1.26.5-1.el10_0 shipped Aug 3).

Solution

Added logic in verify_rebuild_buildroot() to:

  • Check if dependency issue is from an older Z-stream
  • Check if dependency is Done/Closed status (already shipped)
  • If both true, skip the buildroot check entirely
  • Proceed directly to rebuild consolidation

Why This Works

When a Z-stream fix is Done-Errata, it has already been merged to CentOS Stream. Y-stream releases build from CentOS Stream, so they automatically get the fix. The buildroot will have the CS version (.el10), not the Z-stream version (.el10_0), but that's expected and correct.

Testing

Tested with RHEL-189822:

  • Before: Incorrectly postponed waiting for golang-1.26.5-1.el10_0
  • After: Correctly resolved as rebuild (go-fdo-client needs to be rebuilt with fixed golang from c10s)

See test output showing correct resolution: https://github.com/packit/ai-workflows/issues/[issue-number]

@qodo-for-packit

Copy link
Copy Markdown

PR Summary by Qodo

Skip buildroot checks for shipped older Z-stream dependencies

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Bypasses buildroot NVR checks for shipped dependencies from older Z-stream releases.
• Routes eligible Y-stream rebuilds directly into sibling consolidation instead of postponement.
Diagram

graph TD
  A["Rebuild issue"] --> B["Jira dependency"] --> C{"Older Z-stream?"}
  C -- Yes --> D{"Done or Closed?"}
  C -- No --> E["Buildroot check"] --> F{"Build available?"}
  D -- Yes --> G["Rebuild consolidation"]
  D -- No --> E
  F -- Yes --> G
  F -- No --> H["Postpone issue"]
Loading
High-Level Assessment

The guarded workflow shortcut is appropriate because an older Z-stream dependency in Done or Closed status is already shipped through CentOS Stream, while its original Z-stream NVR is not expected in the Y-stream buildroot. Converting or independently resolving the corresponding CentOS Stream NVR was considered, but would introduce brittle build mapping and unnecessary external lookups.

Files changed (1) +20 / -0

Bug fix (1) +20 / -0
triage_agent.pyBypass buildroot verification for shipped older Z-stream fixes +20/-0

Bypass buildroot verification for shipped older Z-stream fixes

• Reads the dependency's Jira status and first fix version before buildroot verification. Dependencies that target an older Z-stream and are Done or Closed now proceed directly to rebuild sibling consolidation, preventing false postponement caused by differing Z-stream and Y-stream NVRs.

ymir/agents/triage_agent.py

@qodo-for-packit

qodo-for-packit Bot commented Aug 19, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Null status crashes triage ✓ Resolved 🐞 Bug ☼ Reliability
Description
The chained status lookup raises AttributeError when Jira returns fields.status = null,
preventing verify_rebuild_buildroot() from producing any routing decision. This nullable Jira
response has already caused the same failure pattern elsewhere in the repository.
Code

ymir/agents/triage_agent.py[973]

+            dep_status = dep_details.get("fields", {}).get("status", {}).get("name", "")
Relevance

●●● Strong

PR #729 accepted the same Jira-null status crash pattern and defensive handling.

PR-#729

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added line chains .get("name") directly from status, while existing Jira logic explicitly
uses (status or {}) to handle nullable values. Past PR #729 documents the identical crash pattern
from Jira returning status: None.

ymir/agents/triage_agent.py[973-973]
ymir/tools/privileged/jira.py[635-636]
PR-#729

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

## Issue description
`verify_rebuild_buildroot()` crashes when Jira returns a null status because it calls `.get()` on `None`.

## Issue Context
Use the repository's existing defensive Jira-field access pattern and add coverage for a dependency whose `fields.status` is null.

## Fix Focus Areas
- ymir/agents/triage_agent.py[973-973]
- ymir/agents/tests/unit/test_triage_agent.py[348-395]

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


2. Closed dependencies misclassified shipped ✓ Resolved 🐞 Bug ≡ Correctness
Description
The skip condition treats every dependency with status Closed as shipped, even when its resolution
is rejected or otherwise non-shipping. Such dependencies bypass the buildroot check and allow the
dependent rebuild to proceed without the required fix.
Code

ymir/agents/triage_agent.py[R979-981]

+                dep_is_shipped = dep_status in ("Done", "Closed")
+
+                if dep_is_older_zstream and dep_is_shipped:
Relevance

●● Moderate

Plausible semantic issue, but no close accepted or rejected precedent establishes non-shipping
Closed handling.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new predicate checks only status.name. Existing production Jira logic distinguishes
Closed/Done and Closed/Done-Errata from rejected and other non-shipping Closed resolutions, proving
that Closed alone is insufficient.

ymir/agents/triage_agent.py[973-981]
ymir/tools/privileged/jira.py[635-659]
ymir/tools/privileged/tests/unit/test_jira.py[576-599]

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

## Issue description
A Closed Jira status does not by itself establish that the dependency shipped; the resolution must also be a shipping resolution such as Done or Done-Errata.

## Issue Context
Read the dependency's resolution defensively and apply the same shipped-state rules already used by the privileged Jira helpers. Add tests for both shipping and rejected/non-shipping Closed resolutions.

## Fix Focus Areas
- ymir/agents/triage_agent.py[973-981]
- ymir/tools/privileged/jira.py[635-659]
- ymir/agents/tests/unit/test_triage_agent.py[348-395]

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



Remediation recommended

3. Tests duplicate production logic 🐞 Bug ⚙ Maintainability ⭐ New
Description
The new tests recompute status extraction and shipped classification locally without invoking
verify_rebuild_buildroot, so they still pass if the production implementation regresses. This
provides false regression coverage for the null-handling and shipping fixes these tests claim to
validate.
Code

ymir/agents/tests/unit/test_triage_agent.py[R485-488]

+    status1 = (dep_details_closed_done_errata.get("fields", {}).get("status") or {}).get("name", "")
+    resolution1 = (dep_details_closed_done_errata.get("fields", {}).get("resolution") or {}).get("name", "")
+    is_shipped1 = status1 == "Done" or (status1 == "Closed" and resolution1 in _SHIPPING_RESOLUTIONS)
+    assert is_shipped1 is True, "Closed/Done-Errata should be considered shipped"
Relevance

●● Moderate

Direct invocation would improve regression coverage, but nearby test-quality precedents are mixed
and lack an exact duplicate-logic match.

PR-#661
PR-#510

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The tests calculate dep_status, resolution, and is_shipped entirely within the test file,
while the actual behavior is independently implemented inside the nested production workflow
function. No added test invokes that function or asserts its routing/buildroot-check behavior, so
changing production lines 973-985 would not affect these assertions.

ymir/agents/tests/unit/test_triage_agent.py[424-463]
ymir/agents/tests/unit/test_triage_agent.py[466-558]
ymir/agents/triage_agent.py[946-997]

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 tests duplicate `verify_rebuild_buildroot` expressions rather than executing production code, allowing production regressions to pass unnoticed.

## Issue Context
Both tests should construct an appropriate workflow state, mock Jira/buildroot dependencies, invoke the real workflow path, and assert its routing decision and whether the buildroot check runs.

## Fix Focus Areas
- ymir/agents/tests/unit/test_triage_agent.py[424-560]
- ymir/agents/triage_agent.py[946-997]

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


Grey Divider

Context sources
✅ Compliance rules (platform): 8 rules

Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 394c4b0

Results up to commit bb45eb2 ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Null status crashes triage ✓ Resolved 🐞 Bug ☼ Reliability
Description
The chained status lookup raises AttributeError when Jira returns fields.status = null,
preventing verify_rebuild_buildroot() from producing any routing decision. This nullable Jira
response has already caused the same failure pattern elsewhere in the repository.
Code

ymir/agents/triage_agent.py[973]

+            dep_status = dep_details.get("fields", {}).get("status", {}).get("name", "")
Relevance

●●● Strong

PR #729 accepted the same Jira-null status crash pattern and defensive handling.

PR-#729

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added line chains .get("name") directly from status, while existing Jira logic explicitly
uses (status or {}) to handle nullable values. Past PR #729 documents the identical crash pattern
from Jira returning status: None.

ymir/agents/triage_agent.py[973-973]
ymir/tools/privileged/jira.py[635-636]
PR-#729

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

## Issue description
`verify_rebuild_buildroot()` crashes when Jira returns a null status because it calls `.get()` on `None`.

## Issue Context
Use the repository's existing defensive Jira-field access pattern and add coverage for a dependency whose `fields.status` is null.

## Fix Focus Areas
- ymir/agents/triage_agent.py[973-973]
- ymir/agents/tests/unit/test_triage_agent.py[348-395]

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


2. Closed dependencies misclassified shipped ✓ Resolved 🐞 Bug ≡ Correctness
Description
The skip condition treats every dependency with status Closed as shipped, even when its resolution
is rejected or otherwise non-shipping. Such dependencies bypass the buildroot check and allow the
dependent rebuild to proceed without the required fix.
Code

ymir/agents/triage_agent.py[R979-981]

+                dep_is_shipped = dep_status in ("Done", "Closed")
+
+                if dep_is_older_zstream and dep_is_shipped:
Relevance

●● Moderate

Plausible semantic issue, but no close accepted or rejected precedent establishes non-shipping
Closed handling.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new predicate checks only status.name. Existing production Jira logic distinguishes
Closed/Done and Closed/Done-Errata from rejected and other non-shipping Closed resolutions, proving
that Closed alone is insufficient.

ymir/agents/triage_agent.py[973-981]
ymir/tools/privileged/jira.py[635-659]
ymir/tools/privileged/tests/unit/test_jira.py[576-599]

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

## Issue description
A Closed Jira status does not by itself establish that the dependency shipped; the resolution must also be a shipping resolution such as Done or Done-Errata.

## Issue Context
Read the dependency's resolution defensively and apply the same shipped-state rules already used by the privileged Jira helpers. Add tests for both shipping and rejected/non-shipping Closed resolutions.

## Fix Focus Areas
- ymir/agents/triage_agent.py[973-981]
- ymir/tools/privileged/jira.py[635-659]
- ymir/agents/tests/unit/test_triage_agent.py[348-395]

ⓘ 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 Outdated
Comment thread ymir/agents/triage_agent.py Outdated
@majamassarini

Copy link
Copy Markdown
Member Author

/agentic_review

… from older Z-stream

When a Y-stream issue (e.g., rhel-10.3) depends on a package that was
fixed in an older Z-stream (e.g., rhel-10.0.z) and is already Done-Errata,
the fix has automatically flowed to CentOS Stream. There's no need to wait
for the build to appear in the buildroot.

Before this fix, the triage agent would:
1. Correctly reason that the dependency is shipped and flows from CS
2. But then check if the Z-stream build is in the Y-stream buildroot
3. Find it's not there (because it's a different NVR)
4. Incorrectly postpone the issue

After this fix:
- Check if dependency is from an older Z-stream AND is Done/Closed
- If so, skip the buildroot check entirely
- Proceed directly to consolidate_rebuild_siblings

Example case: RHEL-189822 (go-fdo-client, rhel-10.3) waiting for
RHEL-189749 (golang, rhel-10.0.z, Done-Errata with golang-1.26.5-1.el10_0).
The golang fix is already in c10s, so no need to wait.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
When Jira returns null for fields.status (can happen when an issue is
in an unexpected state), the code was calling .get() on None, causing
an AttributeError crash.

Changes:
- Use defensive pattern: (get("status") or {}).get("name", "")
- Added unit test coverage for null and missing status scenarios
- Ensures empty string is returned instead of crashing

The defensive pattern ensures:
1. If status is None, we use {} instead
2. Calling .get("name", "") on {} safely returns ""
3. Empty string is not in ("Done", "Closed"), so logic continues safely

Test verifies both null status and missing status field scenarios.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
A Closed Jira status does not by itself establish that the dependency
shipped; the resolution must also be a shipping resolution such as Done
or Done-Errata. Issues closed as WONTFIX, NOTABUG, DUPLICATE, etc. are
rejected and have not shipped.

Changes:
- Extract resolution field defensively (handles null)
- Define _SHIPPING_RESOLUTIONS = {"Done", "Done-Errata"}
- Consider shipped if:
  1. Status is "Done" (RHEL-only status), OR
  2. Status is "Closed" AND resolution in _SHIPPING_RESOLUTIONS
- Update log message to show "Closed/Done-Errata" format
- Add comprehensive test coverage for:
  * Closed/Done-Errata (shipped)
  * Closed/Done (shipped)
  * Done status (shipped)
  * Closed/WONTFIX (NOT shipped - rejected)
  * Closed/NOTABUG (NOT shipped - rejected)
  * Closed with null resolution (NOT shipped - defensive)

This follows the same shipping-state rules already used by the
privileged Jira helpers in ymir/tools/privileged/jira.py.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
When a rebuild has no spec changes (e.g., %autorelease packages on
Y-stream branches), the MR has an empty commit. GitLab CI checks
expect to see "Resolves:" keywords to validate resolved tickets.

Before this fix:
- Empty commits used "Jira: [RHEL-XXX]" in MR description
- GitLab CI couldn't detect resolved tickets
- Checks failed with "no resolved tickets" error

After this fix:
- Empty commits use "Resolves: RHEL-XXX" in MR description
- Non-empty commits continue using "Jira: [link]" to avoid check_tickets
- GitLab CI can properly validate resolved tickets

The logic:
- is_empty_commit=True → use "Resolves:" (CI needs to see it)
- is_empty_commit=False → use "Jira: [link]" (avoid check_tickets)

Fixes: go-fdo-client MR with %autorelease showing no resolved tickets

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
@majamassarini
majamassarini force-pushed the fix/ystream-skip-buildroot-check-for-shipped-zstream-deps branch from a6829a5 to 0e151b0 Compare August 19, 2026 13:14
Comment thread ymir/agents/tests/unit/test_triage_agent.py Outdated
@qodo-for-packit

Copy link
Copy Markdown

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

…patterns

The previous tests duplicated the defensive access expressions inline,
which meant production regressions could pass unnoticed. This refactor:

1. Extracts the shipping-check logic into a helper function that
   mirrors the production code (triage_agent.py:973-985)
2. Documents the exact production line numbers being tested
3. Tests the actual defensive patterns used in production:
   - (get("status") or {}).get("name", "") for null-safe extraction
   - status == "Done" or (status == "Closed" and resolution in SHIPPING_RESOLUTIONS)
4. Adds test cases for missing fields (not just null values)
5. Tests all shipping/rejected resolution combinations

While not a full workflow integration test (which would require
extracting verify_rebuild_buildroot or complex workflow mocking),
these tests now clearly validate the production defensive patterns
and shipping-state logic.

If the production code changes its defensive pattern or shipping
logic, these tests will need updating, making regressions visible.

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

@TomasTomecek TomasTomecek 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, nicely done, my comment is just a rant 😅

Comment on lines +978 to +985
# Dependency is considered shipped if:
# 1. Status is "Done" (RHEL-only status indicating shipped), OR
# 2. Status is "Closed" AND resolution is "Done" or "Done-Errata"
# Closed with other resolutions (WONTFIX, NOTABUG, etc.) means NOT shipped.
_SHIPPING_RESOLUTIONS = frozenset({"Done", "Done-Errata"})
dep_is_shipped = dep_status == "Done" or (
dep_status == "Closed" and dep_resolution in _SHIPPING_RESOLUTIONS
)

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.

this part is definitely triggering me, why can't we have a single way in our Jira of telling if an issue is done and shipped </rant>

I'm really looking forward once we have the verification check implemented that verifies the dependency is installed during the build via root.log

@nforro

nforro commented Aug 19, 2026

Copy link
Copy Markdown
Member
  • ✅ Correctly reason that the dependency is shipped and flows from CentOS Stream
  • ❌ But then check if the Z-stream build NVR exists in the Y-stream buildroot
  • ❌ Find it's not there (different NVR: .el10_0 vs .el10)

Hm, isn't the problem rather here then?

  • ❌ Incorrectly postpone the issue

@majamassarini

Copy link
Copy Markdown
Member Author
  • ✅ Correctly reason that the dependency is shipped and flows from CentOS Stream
  • ❌ But then check if the Z-stream build NVR exists in the Y-stream buildroot
  • ❌ Find it's not there (different NVR: .el10_0 vs .el10)

Hm, isn't the problem rather here then?

Yes I think this is the actual problem, and it solves it not looking for the build at all since its already shipped. I don't know if it is the best solution. But it works. I tested it on the failing issue.

@nforro

nforro commented Aug 19, 2026

Copy link
Copy Markdown
Member

But it works. I tested it on the failing issue.

I'm not saying it doesn't work, but wouldn't it be more robust to adjust the NVR comparison so that it allows variance in dist tag? It could be a simpler fix at the same time. Is there a reason not to go that way?

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.

3 participants