Skip to content

Canonicalize cross-stream MR titles and changelog entries by issue family - #828

Merged
nforro merged 3 commits into
packit:mainfrom
nforro:consistency
Sep 18, 2026
Merged

nforro merged 3 commits into
packit:mainfrom
nforro:consistency

Conversation

@nforro

@nforro nforro commented Sep 14, 2026 •

Copy link
Copy Markdown
Member

Summary

Makes commit subjects, MR titles, and changelog entries consistent across related Jira issues and RHEL streams.

The workflow prefers durable human-authored downstream metadata when available; Redis is a short-lived coordination cache.

Metadata Selection

Normal backports use this precedence:

  1. A single explicit RHEL dist-git source commit (older Z-Streams only).
  2. A resolved same-family sibling Jira's shipped-build source commit.
  3. Redis family cache.
  4. CVE Jira summary.
  5. TitleAgent-generated title for non-CVE issues.

A selected source commit provides:

  • Its subject for the resulting commit and MR title.
  • Its newest spec changelog entry for changelog wording.

Multiple explicit source commits retain the existing combined-changelog behavior and do not select an arbitrary source title.

Resolved Sibling Recovery

For backports without an explicit source title, Ymir:

  1. Finds resolved same-CVE or same-Cloners-family/package Jiras with Fixed in Build.
  2. Selects the latest by Jira resolutiondate.
  3. Resolves its Brew NVR to a validated RHEL or CentOS Stream dist-git commit.
  4. Reuses that commit's subject and changelog.

This restores consistent human-authored wording even after Redis expires or is cleared.

Redis And Safety

  • CVEs are grouped by normalized CVE set; non-CVEs by Jira Cloners root.
  • Redis elects one title per package/family and expires records after 30 days.
  • Only a newer update from the source Jira can replace a cached title.
  • Mixed-family consolidations do not use an individual family cache record.
  • TitleAgent is separate from LogAgent and has no file or shell tools.
  • Untrusted Jira, source changelog, and commit context is delimited in prompts.
  • Titles and changelog entries are validated, RPM-macro escaped, and checked deterministically after generation.

Tests and documentation cover source recovery, cache behavior, CVE parsing, and changelog consistency.

Fixes https://redhat.atlassian.net/browse/PACKIT-5208.

@qodo-for-packit

Copy link
Copy Markdown

PR Summary by Qodo

Canonicalize cross-stream titles by issue family

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Canonicalizes CVE titles from Jira summaries across rebase, backport, and rebuild workflows.
• Shares non-CVE titles atomically by package and Jira Cloners-chain root.
• Versions Redis records by summary digest and applies titles consistently to changelogs.
Diagram

graph TD
  W["Package Workflows"] --> J["Jira Details"] --> D{"CVE Family?"}
  D -->|CVE| S["Summary Title"] --> R["Atomic Redis Record"] --> O["Commit MR Changelog"]
  D -->|Non-CVE| C["Clone Root"] --> T["Title Agent"] --> R
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Deterministic local title generation
  • ➕ Avoids Redis coordination and title-agent race handling.
  • ➕ Produces titles without retained shared state.
  • ➖ Cannot guarantee identical titles when sibling workflows have different change context.
  • ➖ Would require a strict deterministic formatter that may produce less descriptive titles.
2. Store canonical titles in Jira
  • ➕ Makes the canonical value visible and editable by users.
  • ➕ Provides durable storage beyond Redis expiration.
  • ➖ Requires Jira schema or field-management changes.
  • ➖ Adds write permissions, synchronization, and stale-value ownership concerns.
  • ➖ Couples workflow execution to mutating issue metadata.

Recommendation: Keep the Redis-backed family record. It builds naturally on queue-mode infrastructure, atomically resolves concurrent workers, supports summary-driven invalidation, and avoids adding Jira write-side state. The resolver and title-agent invocation could later be centralized further to reduce duplicated workflow integration code.

Files changed (16) +588 / -81

Enhancement (11) +338 / -79
backport_agent.pyApply canonical titles throughout backport workflows +64/-17

Apply canonical titles throughout backport workflows

• Resolves canonical titles for normal and inherited backports, supplies them to the Log Agent, and forces inherited commit subjects to match. Replaces the operation-specific MR metadata cache integration.

ymir/agents/backport_agent.py

log_agent.pyAdd a dedicated non-CVE title agent +19/-0

Add a dedicated non-CVE title agent

• Introduces a constrained title-only agent and prompt accessor for generating concise, user-facing canonical titles independently of full changelog generation.

ymir/agents/log_agent.py

package_update_steps.pyTrack canonical titles in package workflow state +1/-0

Track canonical titles in package workflow state

• Adds an optional canonical title field shared by package update workflow states.

ymir/agents/package_update_steps.py

instructions.j2Require verbatim canonical titles in log output +8/-3

Require verbatim canonical titles in log output

• Directs the Log Agent to preserve canonical titles exactly in changelog descriptions and output titles, including titles exceeding the normal length limit.

ymir/agents/prompts/log/instructions.j2

prompt.j2Pass canonical titles to the Log Agent +6/-0

Pass canonical titles to the Log Agent

• Renders an explicit canonical-title section requiring exact reuse for commits, merge requests, and changelogs.

ymir/agents/prompts/log/prompt.j2

title_instructions.j2Define canonical title generation rules +7/-0

Define canonical title generation rules

• Adds instructions for concise non-CVE titles based on implemented user-facing changes, excluding Jira keys and preferring accurate source changelog wording.

ymir/agents/prompts/log/title_instructions.j2

title_prompt.j2Add the non-CVE title generation prompt +12/-0

Add the non-CVE title generation prompt

• Provides Jira summary context, implemented changes, and optional source changelog wording to the title-only agent.

ymir/agents/prompts/log/title_prompt.j2

rebase_agent.pyResolve canonical titles during rebases +35/-14

Resolve canonical titles during rebases

• Fetches and applies canonical titles before log generation in Redis queue mode. Propagates CVE identifiers through initial, environment-driven, and retry workflow paths.

ymir/agents/rebase_agent.py

rebuild_agent.pyResolve canonical titles during rebuilds +36/-1

Resolve canonical titles during rebuilds

• Integrates canonical title resolution into rebuild log generation and enforces the resolved title afterward. Carries CVE identifiers through workflow execution and retries.

ymir/agents/rebuild_agent.py

tasks.pyImplement atomic issue-family title resolution +125/-37

Implement atomic issue-family title resolution

• Replaces operation-specific MR metadata caching with summary-versioned canonical records keyed by normalized CVE sets or Jira clone roots. Fetches current Jira data, generates only non-CVE cache misses, uses atomic Redis SET NX, and expires records after 30 days.

ymir/agents/tasks.py

models.pyModel canonical title inputs and Redis records +25/-7

Model canonical title inputs and Redis records

• Adds canonical title fields and dedicated title-agent schemas. Revises cached metadata to store issue-family identity and summary digest instead of operation-specific details.

ymir/common/models.py

Refactor (1) +1 / -1
reproducer_lock.pyGeneralize clone-parent warning terminology +1/-1

Generalize clone-parent warning terminology

• Removes reproducer-lock-specific wording so clone-root resolution can be reused by canonical title handling.

ymir/common/reproducer_lock.py

Tests (2) +228 / -1
test_jinja2_templates.pyTest canonical and generated title prompts +23/-0

Test canonical and generated title prompts

• Extends local prompt schemas and verifies Jira summaries remain contextual while canonical titles are rendered as exact output requirements.

ymir/agents/tests/unit/test_jinja2_templates.py

test_tasks.pyTest canonical title identity and concurrency behavior +205/-1

Test canonical title identity and concurrency behavior

• Covers atomic CVE publication, first-writer selection for non-CVEs, required generated titles, summary invalidation, CVE normalization, clone-root grouping, and generator bypass behavior.

ymir/agents/tests/unit/test_tasks.py

Documentation (2) +21 / -0
README-agents.mdDocument cross-stream canonical title behavior +20/-0

Document cross-stream canonical title behavior

• Explains CVE and non-CVE family identification, atomic title election, summary-based invalidation, expiration, and changelog behavior. Clarifies that direct and dry-run workflows bypass canonical records.

README-agents.md

data_retention_policy.mdRecord canonical title retention policy +1/-0

Record canonical title retention policy

• Adds canonical MR titles as Redis-backed data with a configured 30-day retention period.

data_retention_policy.md

@qodo-for-packit

qodo-for-packit Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

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

⚠️ 25 lower-priority findings omitted to fit the comment size limit; re-run the review or view the findings in the Qodo portal.

Grey Divider


Action required

1. Malformed dates abort sibling recovery ✓ Resolved 🐞 Bug ☼ Reliability ⭐ New
Description
_parse_resolutiondate returns every ISO datetime accepted by datetime.fromisoformat, including
timezone-naive and timezone-aware values, which discover_resolved_sibling_candidates then sorts
without normalization or exception handling. When Jira’s candidate response mixes those forms, the
direct discovery call raises before the backport can reach sibling metadata recovery or normal title
fallback.
Code

ymir/agents/backport_agent.py[454]

+    return sorted(candidates, key=lambda candidate: candidate.resolutiondate, reverse=True)
Relevance

●●● Strong

Mixed naive and aware datetimes cause a concrete runtime failure during candidate sorting.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
datetime.fromisoformat produces a naive datetime when the input has no offset and an aware
datetime when an offset is present, and _parse_resolutiondate does not check timezone awareness
before returning parsed values. Candidate discovery sorts all retained values together, while its
caller awaits the operation without a surrounding fallback handler, so mixed naive and aware
timestamps reach an invalid comparison directly.

ymir/agents/backport_agent.py[379-385]
ymir/agents/backport_agent.py[438-454]
ymir/agents/backport_agent.py[1568-1579]
ymir/agents/backport_agent.py[1567-1579]

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

## Issue description
Resolved sibling candidates can contain both timezone-aware and timezone-naive `resolutiondate` values. Sorting that mixed set raises `TypeError`, causing optional sibling metadata recovery to interrupt the backport workflow before ordinary title generation can run.

## Fix Focus Areas
- ymir/agents/backport_agent.py[379-385]
- ymir/agents/backport_agent.py[438-454]

## Recommended Fix
After parsing, reject timestamps whose `tzinfo` or UTC offset is absent by returning `None`, and normalize accepted timezone-aware timestamps to UTC before storing and sorting candidates. Alternatively, normalize a documented naive Jira format before candidate construction; in either case, ensure all retained candidate datetimes are mutually comparable in UTC.

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


2. Backports fail during Brew outages ✓ Resolved 🐞 Bug ☼ Reliability ⭐ New
Description
resolve_resolved_sibling_source catches only InheritCandidateError around resolve_brew_source,
although the new Brew lookup can also propagate operational exceptions from its threaded Koji
request. A transient lookup failure while examining any shipped or resolved sibling escapes
run_log_agent, so the backport fails rather than trying another candidate or ordinary title
generation.
Code

ymir/agents/backport_agent.py[R482-486]

+            source = await resolve_brew_source(
+                candidate.fixed_in_build,
+                package,
+                allowed_namespaces=("rhel", "centos-stream"),
+            )
Relevance

●●● Strong

Operational Brew failures escaping recovery directly violate fallback behavior; recent workflow
reviews favor isolating such failures.

PR-#726

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new candidate loop handles validation failures only, but resolve_brew_source begins with an
uncaught threaded Koji lookup. The new run_log_agent invokes this recovery directly before its
ordinary title fallback, so an operational lookup exception bypasses that fallback.

ymir/agents/backport_agent.py[481-494]
ymir/agents/backport_agent.py[1553-1563]
ymir/agents/ystream_inherit.py[99-106]

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-source recovery only catches `InheritCandidateError` around Brew provenance lookup. Transport, authentication, or Koji client failures from `resolve_brew_source` escape the optional metadata-recovery path and abort the backport instead of allowing the next sibling or normal title generation.

## Fix Focus Areas
- ymir/agents/backport_agent.py[481-494]

## Recommended Fix
Catch ordinary operational exceptions from `resolve_brew_source` in the candidate loop, log the failed NVR with traceback, and continue to the next candidate. Preserve cancellation semantics by not catching `BaseException`; when every candidate fails, return `None` so the existing ordinary title fallback proceeds.

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


3. Issue summaries execute package macros ✓ Resolved 🐞 Bug ⛨ Security
Description
ensure_canonical_changelog_title and the inherited changelog path interpolate canonical titles
into RPM specs without escaping %, while _validate_canonical_title accepts RPM macro syntax. A
crafted CVE Jira summary therefore reaches committed spec content and, for inherited backports, the
subsequent rpmbuild preparation and source-package commands.
Code

ymir/agents/tasks.py[R980-981]

+                if line.strip() and not is_jira_reference:
+                    entry.content[index] = f"- {title}"
Relevance

●●● Strong

Clear RPM macro injection risk from unescaped external titles; recent history accepts defensive
validation and changelog safety fixes.

PR-#598
PR-#477

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Canonical-title validation rejects only length and selected Unicode categories, so % and RPM macro
delimiters remain valid. Both changelog writers insert the title directly, and the inherited path
immediately invokes tools that execute rpmbuild against the modified spec.

ymir/agents/tasks.py[897-906]
ymir/agents/tasks.py[977-982]
ymir/agents/backport_agent.py[929-956]
ymir/tools/unprivileged/wicked_git.py[181-185]

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

## Issue description
Canonical titles are untrusted Jira or generated display data, but they are written directly into RPM spec changelogs. Percent-prefixed RPM macro syntax can consequently be interpreted when the spec is parsed or built.

## Fix Focus Areas
- ymir/agents/tasks.py[954-982]
- ymir/agents/backport_agent.py[929-933]

## Recommended Fix
Escape RPM macro metacharacters at every spec-changelog write boundary, including both deterministic correction and inherited changelog insertion, while preserving the original title for commit and merge-request titles. Add tests using percent-prefixed macro expressions to verify the stored spec safely represents literal text.

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


View high (6)
4. Older summaries win cache races ✓ Resolved 🐞 Bug ≡ Correctness
Description
resolve_canonical_mr_title rereads and returns the SET NX winner without comparing its summary
digest or update timestamp to the caller's newer Jira data. When an older worker wins an initially
empty cache while a newer-summary worker is publishing concurrently, the newer worker uses the stale
title for its changelog, commit, and merge request.
Code

ymir/agents/tasks.py[R1028-1032]

+    cached = await _get_cached_canonical_metadata(redis_conn, cache_key, jira_issue, bool(cve_ids))
+    if cached is None:
+        raise RuntimeError(f"Canonical MR title disappeared after atomic create for {jira_issue}")
+    logger.info("Reused canonical MR title for %s from %s", jira_issue, cache_key)
+    return cached[0].title
Relevance

●●● Strong

Clear concurrency correctness bug contradicting the documented timestamp guarantee; similar Redis
race handling was accepted recently.

PR-#610

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The initial publication uses SET NX, so whichever worker arrives first wins regardless of Jira
summary age. The loser then rereads and returns that record without applying the timestamp checks
used for an ordinary cache hit, contradicting the documented guarantee that an older worker cannot
preserve an obsolete title over newer summary data.

ymir/agents/tasks.py[1008-1032]
ymir/agents/tasks.py[984-990]
README-agents.md[46-48]

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 worker that loses the initial `SET NX` race immediately returns the winning title, even when its own Jira summary has a newer update timestamp. This lets an older snapshot remain canonical and makes the newer worker use a stale title.

## Fix Focus Areas
- ymir/agents/tasks.py[1008-1032]
- ymir/agents/tests/unit/test_tasks.py[72-121]

## Recommended Fix
After a failed `SET NX`, compare the reread record's digest and `summary_updated` with the caller's values using the same invalidation rules as the initial cache-hit path. If the caller is newer, conditionally replace the winner with Lua and retry on another race; add a concurrent cache-miss regression test where the newer summary loses the initial election but ultimately becomes canonical.

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


5. Sibling workers publish different titles ✓ Resolved 🐞 Bug ≡ Correctness
Description
_canonical_mr_title_key includes the current issue's raw summary digest in addition to the shared
family identity, so siblings only contend on one Redis key when their summaries are byte-for-byte
identical. When cloned summaries differ or a bulk summary update is still rolling out, each worker
misses independently and publishes a separate canonical title for the same family.
Code

ymir/agents/tasks.py[R947-949]

+    identity_digest = hashlib.sha256(issue_identity.encode()).hexdigest()[:16]
+    summary_digest = hashlib.sha256(jira_summary.encode()).hexdigest()[:16]
+    return f"mr_metadata:v2:{package}:{identity_digest}:{summary_digest}"
Relevance

●●● Strong

Summary digest should version records, not isolate siblings; this contradicts the PR’s stated
family-wide sharing intent.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The family is normalized to a CVE set or clone root, but the key then adds the primary issue's
summary digest. All cache reads and SET NX publication use that summary-specific key, while rebase
and rebuild explicitly pass sibling issues expecting family-wide sharing.

ymir/agents/tasks.py[941-949]
ymir/agents/tasks.py[965-992]
ymir/agents/tasks.py[1062-1089]
ymir/agents/rebase_agent.py[487-495]
ymir/agents/rebuild_agent.py[196-204]

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

## Issue description
Canonical-title keys combine the shared issue-family identity with each worker's Jira summary digest. Siblings whose summaries differ therefore publish separate records instead of participating in one atomic title election.

## Fix Focus Areas
- ymir/agents/tasks.py[933-949]
- ymir/agents/tasks.py[1073-1090]

## Recommended Fix
Use a stable package-and-family Redis key, store the summary version in its metadata, and atomically compare/update that version when publishing. Use a monotonic Jira update version or equivalent ordering guard so an older worker cannot replace the current family record.

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


6. Changelog titles can diverge ✓ Resolved 🐞 Bug ≡ Correctness
Description
The workflows overwrite log_output.title with state.canonical_title only after LogAgent has
already modified the spec changelog, without validating the descriptive line it wrote. When the
agent paraphrases or otherwise fails to copy the supplied value exactly, the commit and merge
request use the canonical title while the staged changelog retains different text.
Code

ymir/agents/backport_agent.py[R1337-1338]

+            if state.canonical_title:
+                log_output.title = state.canonical_title
Relevance

●●● Strong

Directly undermines the PR’s stated changelog canonicalization goal; prior changelog correctness
fixes were accepted.

PR-#477

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
LogAgent has direct spec-editing tools and is instructed to add the changelog entry itself, while
the added workflow code only replaces the returned title afterward. The corrected result is
subsequently used for commit and merge request titles, so no code establishes the same postcondition
for the already-modified changelog.

ymir/agents/log_agent.py[59-89]
ymir/agents/prompts/log/instructions.j2[13-30]
ymir/agents/rebase_agent.py[496-510]
ymir/agents/rebase_agent.py[527-539]
ymir/agents/rebuild_agent.py[206-220]

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

## Issue description
Normal backport, rebase, and rebuild flows trust LogAgent to copy the canonical title into the spec changelog, then only correct the returned title after the agent has already edited the file. This can leave the changelog inconsistent with the commit and merge request titles.

## Fix Focus Areas
- ymir/agents/backport_agent.py[1323-1338]
- ymir/agents/rebase_agent.py[496-510]
- ymir/agents/rebuild_agent.py[206-220]

## Recommended Fix
After LogAgent finishes, deterministically inspect the newly added changelog entry and verify that its descriptive line equals `state.canonical_title` exactly. Correct the entry through a non-LLM helper or fail the workflow before staging when it differs, and reuse the same verification helper in all three workflows.

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


7. Consolidated titles omit sibling context ✓ Resolved 🐞 Bug ≡ Correctness
Description
resolve_current_canonical_mr_title receives only the primary issue and CVE identity even when the
rebase or rebuild state contains consolidated issues from other families. When such issues are
consolidated, an existing primary-family title replaces the multi-issue title, or a newly generated
multi-issue title is cached for later single-family work through the primary family's key.
Code

ymir/agents/rebase_agent.py[R490-493]

+                        package=state.package,
+                        jira_issue=state.jira_issue,
+                        cve_id=state.cve_id,
+                        generate_title=generate_title,
Relevance

●●● Strong

Recent consolidation precedents accept correctness fixes involving sibling state and cross-workflow
consistency.

PR-#726
PR-#785

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Sibling discovery groups issues by component, fix version, workflow labels, status, and comments
rather than CVE identity or Cloners ancestry. The consolidated issue model carries no family
identity, while both changed callers pass only the primary issue and CVE to the resolver and then
overwrite the generated title with the returned primary-family title.

ymir/agents/rebase_consolidation.py[45-65]
ymir/agents/rebase_consolidation.py[589-655]
ymir/agents/rebuild_consolidation.py[33-48]
ymir/common/models.py[355-367]
ymir/agents/tasks.py[844-860]
ymir/agents/tasks.py[914-973]
ymir/agents/rebase_agent.py[487-510]
ymir/agents/rebuild_agent.py[196-220]

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

## Issue description
Canonical-title resolution scopes consolidated rebase and rebuild work only to the primary Jira/CVE family, although consolidated issues are not guaranteed to belong to that family. This can apply a primary-only title to multi-family work or cache a multi-family title for later single-family workers.

## Fix Focus Areas
- ymir/agents/rebase_agent.py[487-509]
- ymir/agents/rebuild_agent.py[196-219]
- ymir/agents/tasks.py[914-973]
- ymir/common/models.py[355-367]

## Recommended Fix
Resolve the family identity for every issue included in a consolidated task. Use the canonical family record only when all included issues resolve to the same family; otherwise generate a title from the complete consolidated change and do not publish or retrieve it through the primary family's canonical cache key.

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


8. Sibling updates replace shared titles ✓ Resolved 🐞 Bug ≡ Correctness
Description
resolve_canonical_mr_title treats any different sibling summary with a newer generic Jira
updated timestamp as a newer version of the family record. When cross-stream siblings have
stream-specific summaries or an unrelated field update, a later worker replaces the elected title
even though no relevant summary changed, so streams can receive different commit, changelog, and
merge-request titles.
Code

ymir/agents/tasks.py[R1003-1005]

+        if cached_metadata.summary_updated and summary_updated <= cached_metadata.summary_updated:
+            logger.info("Kept newer canonical MR title for %s from %s", jira_issue, cache_key)
+            return cached_metadata.title
Relevance

●● Moderate

Potential shared-cache ordering bug is plausible, but no close precedent confirms rejecting
sibling-local timestamp invalidation.

PR-#726

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Redis key is based only on package and family identity, while the digest and update timestamp
come from the currently processed sibling. Repository test data demonstrates stream-specific Jira
summary suffixes, so different summaries within one cross-stream family are legitimate; the
replacement predicate nevertheless interprets the sibling-local timestamp as ordering the shared
record.

ymir/agents/tasks.py[961-969]
ymir/agents/tasks.py[993-1005]
ymir/agents/tasks.py[1047-1061]
ymir/agents/tasks.py[1095-1102]
ymir/agents/tests/unit/test_triage_agent.py[413-426]

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 family-wide canonical-title record stores a digest and generic Jira update timestamp from whichever sibling last published it. Another sibling with a naturally different summary and a later unrelated Jira update can therefore replace the elected title without an actual change to the title source.

## Fix Focus Areas
- ymir/agents/tasks.py[993-1005]
- ymir/agents/tasks.py[1015-1021]
- ymir/agents/tasks.py[1047-1061]
- ymir/common/models.py[887-893]

## Recommended Fix
Record the Jira issue that supplied the canonical record's summary and only invalidate that record when the same source issue's summary digest changes with a newer timestamp. Family siblings encountering a record from another issue must reuse it rather than treating their different summary and generic update timestamp as a replacement version; add tests covering different sibling summaries and unrelated newer updates.

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


9. Jira text changes package commits ✓ Resolved 🐞 Bug ⛨ Security
Description
resolve_canonical_mr_title assigns the Jira summary directly as a CVE title, and the log prompt
interpolates that title as instruction-bearing text without a data boundary. When an actor can
modify a CVE Jira summary, injected instructions reach LogAgent's shell and file-writing tools in
the dist-git clone, whose index is subsequently committed and pushed.
Code

ymir/agents/tasks.py[886]

+    title = jira_summary if cve_ids else generated_title
Relevance

●● Moderate

Security concern is plausible, but history lacks a closely matching precedent for prompt-injection
boundaries in this flow.

PR-#571
PR-#598

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new flow fetches the Jira summary, selects it verbatim as a CVE canonical title, and embeds it
in the LogAgent prompt. That agent has shell and write tools in the working clone, and the workflow
commits and pushes the resulting index.

ymir/agents/tasks.py[923-931]
ymir/agents/tasks.py[877-888]
ymir/agents/prompts/log/prompt.j2[5-9]
ymir/agents/log_agent.py[59-75]
ymir/tools/unprivileged/commands.py[102-125]
ymir/agents/backport_agent.py[1323-1338]
ymir/agents/tasks.py[451-496]

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

## Issue description
CVE Jira summaries now become canonical titles and are interpolated into the write-capable LogAgent prompt. A Jira summary containing model instructions can therefore influence tool use rather than being handled solely as a title value.

## Fix Focus Areas
- ymir/agents/tasks.py[874-911]
- ymir/agents/prompts/log/prompt.j2[5-9]
- ymir/agents/backport_agent.py[1323-1338]

## Recommended Fix
Validate canonical titles as bounded, single-line display data before storing or using them, and pass the value to changelog/commit construction without placing it in an instruction-bearing agent prompt. If LogAgent must receive it, serialize it in a clearly delimited data field and add an explicit instruction that its contents are untrusted data that must never be interpreted as commands or tool instructions; prefer deterministic changelog insertion for the canonical descriptive line.

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



Remediation recommended

10. Deep clone siblings lose source wording ✗ Dismissed 🐞 Bug ≡ Correctness ⭐ New
Description
discover_resolved_sibling_candidates resolves the root of a clone chain but searches only the root
and issues directly linked to it. When a resolved source belongs to a grandchild or deeper
descendant, recovery misses its durable title and changelog and falls through to Redis or generated
wording.
Code

ymir/agents/backport_agent.py[420]

+        family_clause = f'(key = "{clone_root}" OR issue in linkedIssues("{clone_root}", "Cloners"))'
Relevance

●●● Strong

Deep clone traversal mismatch is a concrete correctness gap in the stated family-wide recovery
behavior.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
resolve_clone_root explicitly walks up to 20 parent links, demonstrating that clone families may
span multiple levels. The new query then uses only key = root OR linkedIssues(root, "Cloners"),
which cannot include descendants linked through an intermediate clone.

ymir/agents/backport_agent.py[402-425]
ymir/common/reproducer_lock.py[111-134]

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

## Issue description
Resolved sibling recovery finds a transitive clone root but searches only the root and its directly linked issues, omitting deeper descendants in the same family.

## Fix Focus Areas
- ymir/agents/backport_agent.py[402-425]
- ymir/common/reproducer_lock.py[111-134]

## Recommended Fix
Traverse the complete Cloners graph, or otherwise obtain every descendant of the resolved root, and filter the resolved sibling candidates against that complete family rather than relying on one `linkedIssues` level.

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


11. Large families miss newest source ✗ Dismissed 🐞 Bug ≡ Correctness ⭐ New
Description
discover_resolved_sibling_candidates requests at most 50 unordered matches and sorts only that
truncated response by resolutiondate. When a family has more than 50 resolved issues, the newest
usable source can be absent and an older title or fallback wording is selected instead.
Code

ymir/agents/backport_agent.py[R431-433]

+            fields=["key", "summary", "resolutiondate", "customfield_10578"],
+            max_results=50,
+        )
Relevance

●●● Strong

Unordered truncation before sorting can deterministically omit the newest candidate; pagination or
ordered JQL is warranted.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The discovery call hard-caps results at 50 without an ORDER BY, then sorts only the returned list.
The search tool sends that limit directly as Jira's maxResults and exposes no pagination offset,
proving later matching issues are unavailable to the local sort.

ymir/agents/backport_agent.py[421-454]
ymir/tools/privileged/jira.py[1836-1898]

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

## Issue description
Resolved sibling discovery sorts only an unordered, 50-result subset, so large issue families can omit the newest usable source.

## Fix Focus Areas
- ymir/agents/backport_agent.py[421-454]
- ymir/tools/privileged/jira.py[1836-1898]

## Recommended Fix
Order the query by descending resolution date and add pagination to the Jira search tool and discovery loop, continuing through results until the newest usable build source is found or all matches are exhausted.

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


12. Unrelated rebuilds share a title ✓ Resolved 🐞 Bug ≡ Correctness
Description
find_rebuild_siblings stores the model-produced analysis.cve_id in each consolidated issue
without verifying it against the sibling Jira summary. When a sibling summary omits its CVE and the
analyzer returns the primary CVE incorrectly, family resolution trusts that supplied value and
applies the primary family’s canonical title to the aggregate rebuild.
Code

ymir/agents/rebuild_consolidation.py[R230-236]

                consolidated.append(
                    ConsolidatedIssue(
                        issue_key=candidate_key,
+                        cve_id=cve_id,
                        dependency_issue=analysis.dependency_issue,
                        dependency_component=analysis.dependency_component,
                    )
Relevance

●●● Strong

Persisting unverified analyzer metadata can misclassify families; recent consolidation precedents
favor validating sibling identity.

PR-#785
PR-#726

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The analyzer is instructed to extract CVEs and its response is directly persisted by the new line.
Canonical-family calculation unions this persisted value with the fetched summary and uses it as the
family identity, so an unverified value changes title-sharing behavior.

ymir/agents/rebuild_consolidation.py[153-178]
ymir/agents/rebuild_consolidation.py[230-236]
ymir/agents/rebuild_consolidation.py[255-310]
ymir/agents/tasks.py[1188-1225]

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

## Issue description
Rebuild consolidation uses the analyzer's free-form CVE output as canonical-family metadata. An incorrect value can make a sibling appear to belong to the primary CVE family and reuse that family's title.

## Fix Focus Areas
- ymir/agents/rebuild_consolidation.py[166-178]
- ymir/agents/rebuild_consolidation.py[230-236]

## Recommended Fix
Populate `ConsolidatedIssue.cve_id` by deterministically extracting CVE IDs from the candidate Jira summary returned by the sibling search (or a freshly fetched Jira summary), rather than from `analysis.cve_id`. Leave the analyzer output available only for applicability analysis if needed.

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


View medium (4)
13. Other packages supply backport titles ✓ Resolved 🐞 Bug ≡ Correctness
Description
_rhel_distgit_commit_hash accepts a commit from any package below the RHEL RPM namespace because
it never compares the URL package with state.package. If such a commit is present and readable in
the upstream clone, its unrelated subject replaces the current package's commit and merge-request
title.
Code

ymir/agents/backport_agent.py[R351-358]

+def _rhel_distgit_commit_hash(url: str) -> str | None:
+    """Return a commit hash only for a RHEL GitLab dist-git commit URL."""
+    parsed = urlparse(url)
+    if parsed.hostname != "gitlab.com" or not parsed.path.startswith("/redhat/rhel/rpms/"):
+        return None
+    if not re.search(r"/-/commit/[a-f0-9]{7,40}$", parsed.path, re.IGNORECASE):
+        return None
+    return _extract_commit_hash(url)
Relevance

●●● Strong

Package-path validation is a deterministic correctness guard consistent with prior accepted
repository/source validation.

PR-#73
PR-#652

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The URL validator checks only the shared namespace and commit suffix, while the integration calls it
without package context and later unconditionally prefers the extracted subject; Brew-based source
validation elsewhere demonstrates the package path can be checked exactly.

ymir/agents/backport_agent.py[351-358]
ymir/agents/backport_agent.py[547-565]
ymir/agents/backport_agent.py[1543-1553]
ymir/agents/ystream_inherit.py[124-126]

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

## Issue description
Source-title extraction accepts RHEL dist-git commits belonging to packages other than the package being backported.

## Fix Focus Areas
- ymir/agents/backport_agent.py[351-358]
- ymir/agents/backport_agent.py[547-565]
- ymir/agents/backport_agent.py[1543-1544]

## Recommended Fix
Pass the current package into source URL validation and require the parsed path to match `/redhat/rhel/rpms/{package}/-/commit/{sha}` exactly before reading or applying its subject.

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


14. CVE families reuse unrelated wording ✓ Resolved 🐞 Bug ≡ Correctness
Description
discover_resolved_sibling_candidates searches for summaries containing each requested CVE but
never verifies that a result's normalized CVE set exactly equals the current family. A resolved
issue covering those CVEs plus another CVE can therefore supply its commit subject and changelog to
a different family.
Code

ymir/agents/backport_agent.py[R394-400]

+    if cve_id:
+        cve_ids = sorted(
+            {cve.strip().replace('"', '\\"') for cve in cve_id.replace(";", ",").split(",") if cve.strip()}
+        )
+        if not cve_ids:
+            return []
+        family_clause = " AND ".join(f'summary ~ "{cve}"' for cve in cve_ids)
Relevance

●●● Strong

Exact family identity is central to this PR and prior consolidation reviews accepted safeguards
against cross-family reuse.

PR-#785
PR-#726

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The family query only applies positive substring clauses and does not request candidate summaries,
so no exact-set comparison occurs before the selected candidate's source metadata is installed.

ymir/agents/backport_agent.py[394-400]
ymir/agents/backport_agent.py[421-452]
ymir/agents/backport_agent.py[1560-1574]
ymir/agents/tasks.py[862-864]

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

## Issue description
Resolved sibling discovery accepts Jira issues whose summaries contain the requested CVEs plus additional CVEs, allowing metadata to cross canonical CVE-family boundaries.

## Fix Focus Areas
- ymir/agents/backport_agent.py[386-452]
- ymir/agents/backport_agent.py[1560-1574]

## Recommended Fix
Request each candidate's summary, normalize its CVEs with the shared extraction logic, and retain the candidate only when that set exactly equals the current normalized CVE set.

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


15. Mixed backports use one title ✓ Resolved 🐞 Bug ≡ Correctness
Description
extract_source_title filters upstream_patches to validated RHEL dist-git URLs before counting
sources, while extract_source_changelog processes every extractable commit URL. When one RHEL
commit appears alongside another explicit source commit, the RHEL subject is selected even though
the changelog combines multiple source changes and aggregate title generation should be preserved.
Code

ymir/agents/backport_agent.py[R549-552]

+    source_commits = list(
+        dict.fromkeys(filter(None, (_rhel_distgit_commit_hash(url) for url in upstream_patches)))
+    )
+    if len(source_commits) != 1:
Relevance

●●● Strong

The finding directly contradicts documented single-source title precedence and preserves established
mixed-source changelog behavior.

PR-#477
PR-#652

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The title helper filters out non-RHEL URLs before checking whether exactly one source remains,
whereas the changelog helper iterates over every upstream patch URL and combines all readable commit
entries. The citations therefore show that title selection and changelog generation operate on
different source sets, contrary to the documented behavior that multiple source commits must not
select a single source title.

ymir/agents/backport_agent.py[547-565]
ymir/agents/backport_agent.py[569-616]
README-agents.md[43-46]
ymir/agents/backport_agent.py[568-616]
ymir/agents/backport_agent.py[1540-1544]

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

## Issue description
`extract_source_title` ignores non-RHEL source commits when counting sources, so a mixed-source backport can select the sole RHEL commit's subject even though multiple explicit source commits are present and their changelogs are combined.

## Fix Focus Areas
- ymir/agents/backport_agent.py[547-565]
- ymir/agents/backport_agent.py[568-616]

## Recommended Fix
Count all distinct extractable source commit URLs before selecting a title. Return a source title only when the complete explicit source list contains exactly one usable commit and that sole commit is a validated RHEL dist-git commit; if any additional commit source is present, return `None` and preserve the existing combined-source changelog behavior and aggregate title generation.

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


16. Malformed builds abort backports ✓ Resolved 🐞 Bug ☼ Reliability
Description
resolve_brew_source converts the newly recorded build_id with an unguarded int(...), while
resolved-sibling recovery catches only InheritCandidateError around that call. A sibling with
non-numeric Brew metadata raises ValueError through run_log_agent instead of being skipped so
another sibling or ordinary title generation can be used.
Code

ymir/agents/ystream_inherit.py[R139-142]

        commit_sha=commit_sha.lower(),
        epoch=epoch,
        version=version,
+        build_id=int(build.get("build_id") or 0),
Relevance

●●● Strong

Ungarded malformed metadata can abort recovery; reliability findings requiring graceful fallback
have been accepted recently.

PR-#611
PR-#726

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The newly added build_id conversion is outside the existing guarded epoch conversion. The new
recovery loop catches only the domain exception, and the normal backport log path invokes that loop
without a local fallback catch.

ymir/agents/ystream_inherit.py[128-143]
ymir/agents/backport_agent.py[470-492]
ymir/agents/backport_agent.py[1539-1579]

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 malformed Brew `build_id` raises `ValueError` while resolving a sibling source, but the sibling-selection loop only handles `InheritCandidateError`. This makes one invalid sibling build abort normal backport log generation rather than allowing fallback behavior.

## Fix Focus Areas
- ymir/agents/ystream_inherit.py[128-143]
- ymir/agents/backport_agent.py[477-492]

## Recommended Fix
Convert invalid `build_id` values to `InheritCandidateError`, as is already done for invalid epochs, or broaden the sibling-resolution exception handling to classify malformed Brew metadata as an unusable candidate. Keep the candidate loop continuing so later candidates and normal title generation remain available.

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


Grey Divider

Context sources
⚠️ Tickets: not configured — ticket URL found in PR but could not be fetched — check ticket provider credentials
✅ Compliance rules (platform): 8 rules
Review mode: ⚖️ Balanced: This push adds substantial runtime logic across Jira discovery, Brew/dist-git validation, source selection, changelog extraction, and workflow integration, creating meaningful behavioral and integration risk.

Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread ymir/agents/rebase_agent.py
Comment thread ymir/agents/tasks.py
@nforro

nforro commented Sep 14, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

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

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 17e666c

@nforro

nforro commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

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

Copy link
Copy Markdown

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

@nforro

nforro commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

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

Copy link
Copy Markdown

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

@nforro

nforro commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

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

Copy link
Copy Markdown

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

@nforro

nforro commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

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

Copy link
Copy Markdown

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

@nforro

nforro commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

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

Copy link
Copy Markdown

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

@nforro

nforro commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

Comment thread ymir/agents/tasks.py
Comment thread ymir/agents/prompts/title/prompt.j2
Comment thread ymir/agents/rebuild_agent.py
@qodo-for-packit

Copy link
Copy Markdown

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

@nforro

nforro commented Sep 15, 2026

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 753fa76

@nforro
nforro force-pushed the consistency branch 2 times, most recently from 2ddcf1c to f48d4fd Compare September 16, 2026 08:32
@antbob

antbob commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

@nforro my concerns here are:

  1. if the fix has been produced and committed by a human it will then be overridden by generated "canonical" changelog when ported to older streams which creates inconsistency.

  2. keeping canonical info in redis is probably not a good idea. its fine if it is just for the cache but there needs to be a reference to the original. if the cache expires or gets wiped out then again it leads to inconsistency.

my 2 cents would be that it should check if there is source changelog first and if there is use it for all subsequent streams instead of generating one. if there isnt a source changelog then it can generate one which would then be re-used for all subsequent streams from there.

while we dont have a rule anywhere in the RHEL process that says the changelog entries must be consistent across streams i've found that it is generally what people expect to happen and they start to complain otherwise.

@nforro

nforro commented Sep 17, 2026

Copy link
Copy Markdown
Member Author

@antbob Hopefully I've addressed your points.

@nforro

nforro commented Sep 17, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

Comment thread ymir/agents/backport_agent.py Outdated
Comment thread ymir/agents/backport_agent.py Outdated
Comment thread ymir/agents/backport_agent.py Outdated
Comment thread ymir/agents/ystream_inherit.py Outdated
Comment thread ymir/agents/rebuild_consolidation.py
@qodo-for-packit

Copy link
Copy Markdown

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

@antbob

antbob commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

@nforro thank you, this is very thorough and should keep the changelogs consistent for us! i dont completely understand the reasoning behind the part that does the recovery when cache is missing (ie why cannot the info be recovered straight from source commit instead) but i guess you have solid reasons for doing that, maybe multi-commit cases. either way, on my side i would be just happy that it works consistently, and not how/why.

…mily

Use canonical CVE and generated non-CVE titles across commits, MRs, and
changelogs. Add Redis-backed family coordination, title validation, safe
changelog handling, and cross-stream consolidation safeguards.

Signed-off-by: Nikola Forró <nforro@redhat.com>
Assisted-by: GPT-5.6 Terra via OpenCode
Assisted-by: GPT-5.6 Sol via OpenCode
When exactly one RHEL dist-git commit is supplied as a backport source,
use its commit subject for the resulting commit and MR title. Preserve
source changelog wording, and retain existing behavior for multiple or
external source commits.

Signed-off-by: Nikola Forró <nforro@redhat.com>
Assisted-by: GPT-5.6 Terra via OpenCode
@nforro

nforro commented Sep 17, 2026

Copy link
Copy Markdown
Member Author

the reasoning behind the part that does the recovery when cache is missing (ie why cannot the info be recovered straight from source commit instead)

IIUC what you mean by this, that's the "upstream Ymir" case, we don't have any source commit to take the metadata from.

@nforro

nforro commented Sep 17, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

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

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 01bfdcf

Find resolved same-family Jira siblings with Fixed in Build metadata.
Select the latest by Jira resolution date, resolve its Brew source
commit in RHEL or CentOS Stream dist-git, and reuse its title
and changelog before Redis or TitleAgent fallback.

Signed-off-by: Nikola Forró <nforro@redhat.com>
Assisted-by: GPT-5.6 Terra via OpenCode
@nforro

nforro commented Sep 18, 2026

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 66cf5ec

@lbarcziova lbarcziova left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

overall design LGTM, couldn't find any issues, thanks!

@nforro
nforro merged commit eb3a6ed into packit:main Sep 18, 2026
14 checks passed
@nforro
nforro deleted the consistency branch September 18, 2026 11:55
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