Skip to content

Conversation

@majamassarini
Copy link
Member

No description provided.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new agent, clones_analyzer_agent.py, to identify and link cloned Jira issues. It also modifies the triage_agent.py to incorporate the new agent into its workflow, postponing triage of Y-stream CVEs until the corresponding Z-stream errata have been shipped. Additionally, the common/models.py file is updated to include schemas for the new agent, and the mcp_server/jira_tools.py file is modified to handle CVE triage eligibility. The Makefile and compose.yaml files are updated to include the new agent.

@majamassarini majamassarini marked this pull request as draft October 28, 2025 14:28
Copy link
Member

@lbarcziova lbarcziova left a comment

Choose a reason for hiding this comment

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

this will be amazing, thanks Maja!

return CVEEligibilityResult(
is_cve=True,
is_eligible_for_triage=False,
when_eligible_for_triage=WhenEligibility.NEVER,
Copy link
Member

Choose a reason for hiding this comment

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

eventually, the embargo can be lifted, so this could be also later? But I don't remember for sure if Jotnar even sees those

Copy link
Member Author

Choose a reason for hiding this comment

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

Searching for assignee = jotnar-project and "Embargo Status" = "True" gives no result. So probably we don't see them.

Comment on lines 444 to 454
# @TODO: is this ok??? Or should I check something else?
return current_status.lower() == "closed"
Copy link
Member

Choose a reason for hiding this comment

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

this looks enough for me, but @opohorel @TomasKorbar or @ljavorsk can confirm

logger = logging.getLogger(__name__)


def get_instructions() -> str:
Copy link
Member

Choose a reason for hiding this comment

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

how reliable is this when running locally? I am wondering if this wouldn't be more straightforward to do via tools using Jira API? Or it's not really straightforward?

Copy link
Member Author

Choose a reason for hiding this comment

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

I would say it is reliable. I don't know if I can write a regexp for it. Because I don't know what to expect in the title. So probably this is safer. And in "this agent" there is still a missing step (one of the reason why this PR is still in draft) I should start suggesting if any clone is missing.

To find other Jira issues which are clones of <JIRA_ISSUE> Jira issue, do the following:
1. Search for other Jira issues which have the same affected component as <JIRA_ISSUE> Jira issue in RHEL Jira project and extract their titles.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shall we use get_jira_details() here to get component name, issue title etc. to make the process more deterministic?

Copy link
Member Author

Choose a reason for hiding this comment

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

It should be using it, since it is the passed tool for all the agent definitions. Or am I missing something?

@majamassarini
Copy link
Member Author

/gemini review

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new clones-analyzer-agent to identify cloned Jira issues and modifies the triage-agent to postpone the triage of Y-stream and maintenance-stream CVEs until their corresponding Z-stream clones are resolved. A new cron job is also added to periodically re-evaluate these postponed issues.

The changes are well-structured and address the goal of handling Y-stream CVEs more appropriately. However, I've found a critical logic error in how Z-stream issues are identified, which would prevent the postponement logic from working as intended. I've also identified several areas for code cleanup, including removing unused code and clarifying confusing logic. Please see my detailed comments for suggestions.

Comment on lines +436 to +437
if not re.match(r"^rhel-\d+\.\d+$", branch.lower()):
return True
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

There is a critical logic error here. The function check_z_stream_errata_shipped is intended to check the status of Z-stream issues, but the regex r"^rhel-\\d+\\.\\d+$" matches Y-streams (e.g., rhel-9.8), not Z-streams (e.g., rhel-9.8.z).

The current logic will check the status of Y-stream clones and incorrectly return True for Z-stream clones without checking their status. This defeats the purpose of postponing Y-stream triage.

The logic should be inverted to check for Z-streams. A simpler way to check for a Z-stream branch is to see if it ends with .z.

Suggested change
if not re.match(r"^rhel-\d+\.\d+$", branch.lower()):
return True
if not branch.lower().endswith('.z'):
return True

Comment on lines +560 to +565
elif JiraLabels.POSTPONED.value in JiraLabels.all_labels():
await tasks.set_jira_labels(
jira_issue=state.jira_issue,
labels_to_remove=[JiraLabels.POSTPONED.value],
dry_run=dry_run
)
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The condition JiraLabels.POSTPONED.value in JiraLabels.all_labels() is always true, which makes this elif block confusing. It will execute for any resolution other than POSTPONED, attempting to remove the POSTPONED label. However, all jotnar_ labels are already removed at the beginning of the workflow (line 629). This block is redundant, causes unnecessary API calls, and should be removed.

Comment on lines +82 to +83
def create_clones_analyzer_agent(mcp_tools: list[Tool], local_tool_options: dict[str, Any]) -> RequirementAgent:
return RequirementAgent(**get_agent_definition(mcp_tools))
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The function create_clones_analyzer_agent is not used anywhere in the codebase. It also has an unused parameter local_tool_options. It should be removed to avoid dead code.


setup_observability(os.environ["COLLECTOR_ENDPOINT"])

dry_run = os.getenv("DRY_RUN", "False").lower() == "true"
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The dry_run variable is initialized but never used within the main function. It should be removed.

Comment on lines +444 to +450
params={"fields": "status, assignee"},
headers=headers,
) as response:
response.raise_for_status()
current_issue = await response.json()
current_status = current_issue.get("fields", {}).get("status", {}).get("name", "")
current_assignee = current_issue.get("fields", {}).get("assignee", {}).get("name", "")
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The current_assignee variable is fetched but never used. It should be removed, and the API call can be optimized to only fetch the status field.

Suggested change
params={"fields": "status, assignee"},
headers=headers,
) as response:
response.raise_for_status()
current_issue = await response.json()
current_status = current_issue.get("fields", {}).get("status", {}).get("name", "")
current_assignee = current_issue.get("fields", {}).get("assignee", {}).get("name", "")
params={"fields": "status"},
headers=headers,
) as response:
response.raise_for_status()
current_issue = await response.json()
current_status = current_issue.get("fields", {}).get("status", {}).get("name", "")

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