Skip to content

OCPQUAL-30: Pull Request Risk Analysis Agent - #602

Open
dgoodwin wants to merge 30 commits into
openshift-eng:mainfrom
dgoodwin:risk-analysis-agent
Open

OCPQUAL-30: Pull Request Risk Analysis Agent#602
dgoodwin wants to merge 30 commits into
openshift-eng:mainfrom
dgoodwin:risk-analysis-agent

Conversation

@dgoodwin

@dgoodwin dgoodwin commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Adds two new skills to be part of a future PR pipeline.

assess-pr-risk attempts to determine a risk level for a PR (given code complexity, historical revert risk of the repo), determine what testing should be run against it. The goal here is to improve quality and pre-merge issue detection, while also reducing cost by skipping jobs that are not required. The skill works with the current set of presubmits, and a small set of optional /payload jobs it knows about. If successful, the savings on not running unnecessary jobs may outweigh the cost of performing the analysis on all PRs. The plugin makes a rough guess on how much it spent/saved on CI testing.

assess-pr-test-results attempts to determine if the testing results (across presubmits and /payload jobs) look suspicious, or if we are seeing any failures that look suspicious.

Includes a start on a few evals: /eval-run plugins/ci/evals/cases/assess-pr-risk/

Summary by CodeRabbit

  • New Features
    • Added PR risk assessment with risk scoring, hotspot-aware CI job run/skip recommendations, and cost estimates.
    • Introduced CI tools to fetch PR test results, fetch CI jobs, and estimate Prow job costs.
    • Added a PR risk analyst agent and expanded the marketplace catalog (including standalone skills).
    • Added assess-pr-risk evaluation definitions and new scenarios for risk scoring/job recommendations.
  • Documentation
    • Added/updated skill docs for PR risk assessment, PR test results analysis, job retrieval, and Prow cost estimation.
  • Chores
    • Updated .gitignore to exclude evaluation run outputs.

@openshift-ci

openshift-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: dgoodwin

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 3, 2026
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This change adds CI skills for PR risk assessment, PR test-result analysis, job reporting, and Prow cost estimation, along with a risk analyst agent, evaluation cases and judges, marketplace registration, and ignored evaluation output paths.

Changes

CI assessment toolkit

Layer / File(s) Summary
PR risk assessment workflow
plugins/ci/agents/pr-risk-analyst.md, plugins/ci/skills/assess-pr-risk/SKILL.md
Defines the risk analyst agent and workflow for scoring PR risk, selecting CI jobs, estimating costs, writing state, and generating reports.
CI data and cost utilities
plugins/ci/skills/fetch-jobs/*, plugins/ci/skills/assess-pr-test-results/*, plugins/ci/skills/prow-job-cost-estimator/*
Adds Sippy job and PR test-result retrieval, failure summaries, and platform-based Prow job cost estimation.
Risk assessment evaluation coverage
plugins/ci/evals/eval-assess-pr-risk.*, plugins/ci/evals/cases/assess-pr-risk/*
Adds evaluation configuration, output validation, deterministic judges, LLM rubrics, thresholds, and three annotated PR cases.
Marketplace and plugin registration
docs/index.html, .gitignore, plugins/ci/.claude-plugin/plugin.json
Registers the new skills and agent, populates standalone skill metadata, bumps the plugin version, and ignores eval/runs/.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Analyst as pr-risk-analyst
  participant Skill as assess-pr-risk
  participant GitHub
  participant Release as openshift/release
  participant Sippy
  participant Cost as prow-job-cost-estimator
  Analyst->>Skill: assess PR URL
  Skill->>GitHub: fetch PR metadata, diff, history, and comments
  Skill->>Release: fetch presubmit job configuration
  Skill->>Sippy: fetch CI metrics and durations
  Skill->>Cost: estimate selected job costs
  Skill-->>Analyst: produce report and JSON state
Loading

Suggested labels: do-not-merge/work-in-progress

Suggested reviewers: kasturinarra, mrunalp

🚥 Pre-merge checks | ✅ 9 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (9 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No Real People Names In Style References ✅ Passed No modified file uses a real person's name as a style reference; only generic examples/placeholders and non-person names appear.
No Assumed Git Remote Names ✅ Passed No changed file hardcodes git remotes; exact origin/upstream remote-command patterns were absent, and remaining mentions are repo/context names.
Git Push Safety Rules ✅ Passed No new git push, force-push, or main/master push instructions were added; the changed skill/agent/eval content is analysis-only.
No Untrusted Mcp Servers ✅ Passed The PR adds CI skills/docs/evals only; I found no new MCP server installs, npx mcp-server invocations, or dependency manifest changes.
Ai-Helpers Overlap Detection ✅ Passed PASS: No duplicate skill/agent names, and closest CI docs were related but not overlapping enough to hit the 60% threshold.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main focus on the new PR risk analysis agent and is specific enough for the changeset.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@dgoodwin dgoodwin changed the title risk analysis agent WIP: Pull Request Risk Analysis Agent Jul 3, 2026
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
plugins/ci/skills/fetch-jobs/SKILL.md (1)

97-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Documented API defaults don't match the script's actual defaults.

This table documents sortField defaulting to current_pass_percentage and sort defaulting to asc at the raw Sippy API level. But fetch_jobs.py always sends explicit values (current_runs / desc per its argparse defaults, lines 199 and 206) and never omits these query params, so a user relying on this doc to predict the script's default ordering would be misled.

Consider clarifying that this table reflects the raw API's behavior when params are omitted, and separately documenting the script's own CLI defaults.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/ci/skills/fetch-jobs/SKILL.md` around lines 97 - 104, The documented
defaults in the fetch-jobs API table do not match the behavior of fetch_jobs.py,
which always passes its own argparse defaults for sortField and sort instead of
omitting them. Update the SKILL.md documentation near the parameters table to
clearly separate the raw Sippy API defaults from the script’s CLI defaults, and
mention the fetch_jobs.py defaults for sortField and sort so users can predict
the actual ordering.
plugins/ci/skills/assess-pr-test-results/fetch_pr_test_results.py (1)

147-150: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused loop variable run_id.

Flagged by ruff (B007): run_id is never used inside the loop body.

♻️ Proposed fix
-    for i, ((run_id, job_name), tests) in enumerate(
+    for i, ((_run_id, job_name), tests) in enumerate(
         sorted(by_job.items(), key=lambda x: x[1][0].get("prow_job_start", ""),
                reverse=True)
     ):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/ci/skills/assess-pr-test-results/fetch_pr_test_results.py` around
lines 147 - 150, The loop in fetch_pr_test_results.py binds run_id but never
uses it, triggering ruff B007. Update the iteration over by_job.items() so the
unused name is removed or replaced with a placeholder in the enumerate loop, and
keep the logic that sorts by prow_job_start and uses job_name/tests unchanged.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@plugins/ci/evals/cases/assess-pr-risk/case-001-olm-hashstructure-upgrade/annotations.yaml`:
- Around line 6-9: The score band in the `annotations.yaml` case is inconsistent
with the `expected_tier` mapping used by `tier_matches_score`. Update the
`expected_score_range` for the `critical` case so it matches the `critical` tier
defined in `eval-assess-pr-risk.yaml` (71-100), and keep the `expected_tier`
value unchanged.

In `@plugins/ci/evals/eval-assess-pr-risk.yaml`:
- Around line 224-259: Add a check for annotations.must_skip_jobs in
eval-assess-pr-risk.yaml so cases fail when an expected skip is not present, and
update must_recommend_jobs to inspect only actual /test and /payload-job command
lines rather than any substring in the full conversation. Reuse the existing
command extraction approach from must_not_recommend_jobs, and apply the same
parsing in must_recommend_jobs so skip explanations no longer count as
recommendations.

In `@plugins/ci/skills/assess-pr-risk/SKILL.md`:
- Around line 26-29: The recent-reverts and merged-PR history commands in
SKILL.md rely on a macOS-only cutoff and fixed result limits, which can truncate
data. Update the gh pr list usage to use a portable 6-month date calculation and
fetch all pages for both the revert search and the merged PR query so
assess-pr-risk gets complete history. Keep the changes within the documented
command snippets around gh pr list and the merged: search qualifier.
- Around line 327-331: The Step 5 state-file write in assess-pr-risk should
ensure `.work/pr-risk` starts empty before saving the new assessment, since
reruns can leave stale JSON that breaks the “exactly one file” requirement.
Update the state-file writing flow to clear/reset `.work/pr-risk` first, then
write only `.work/pr-risk/<org>-<repo>-<pr_number>.json`, keeping any
intermediate artifacts in `/tmp/` and preserving the one-file contract.

In `@plugins/ci/skills/prow-job-cost-estimator/estimate_cost.py`:
- Around line 42-48: The aggregations in estimate_cost.py are silently dropping
jobs with unknown decision or ci_status values, which can distort the PR risk
report. Update the logic around the run_jobs/skip_jobs filtering and the
recommended_cost, savings, and added calculations to explicitly validate each
job’s decision and ci_status before summing. If any job has an unexpected value,
raise an error instead of excluding it so malformed upstream input is surfaced
immediately.

---

Nitpick comments:
In `@plugins/ci/skills/assess-pr-test-results/fetch_pr_test_results.py`:
- Around line 147-150: The loop in fetch_pr_test_results.py binds run_id but
never uses it, triggering ruff B007. Update the iteration over by_job.items() so
the unused name is removed or replaced with a placeholder in the enumerate loop,
and keep the logic that sorts by prow_job_start and uses job_name/tests
unchanged.

In `@plugins/ci/skills/fetch-jobs/SKILL.md`:
- Around line 97-104: The documented defaults in the fetch-jobs API table do not
match the behavior of fetch_jobs.py, which always passes its own argparse
defaults for sortField and sort instead of omitting them. Update the SKILL.md
documentation near the parameters table to clearly separate the raw Sippy API
defaults from the script’s CLI defaults, and mention the fetch_jobs.py defaults
for sortField and sort so users can predict the actual ordering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 1cec3ba1-f4bf-4c37-8515-1d1c57755d99

📥 Commits

Reviewing files that changed from the base of the PR and between 4e91f57 and c683d0f.

📒 Files selected for processing (19)
  • .gitignore
  • docs/data.json
  • docs/index.html
  • plugins/ci/agents/pr-risk-analyst.md
  • plugins/ci/evals/cases/assess-pr-risk/case-001-olm-hashstructure-upgrade/annotations.yaml
  • plugins/ci/evals/cases/assess-pr-risk/case-001-olm-hashstructure-upgrade/input.yaml
  • plugins/ci/evals/cases/assess-pr-risk/case-002-origin-bug-id-swap/annotations.yaml
  • plugins/ci/evals/cases/assess-pr-risk/case-002-origin-bug-id-swap/input.yaml
  • plugins/ci/evals/cases/assess-pr-risk/case-003-origin-ote-bump/annotations.yaml
  • plugins/ci/evals/cases/assess-pr-risk/case-003-origin-ote-bump/input.yaml
  • plugins/ci/evals/eval-assess-pr-risk.md
  • plugins/ci/evals/eval-assess-pr-risk.yaml
  • plugins/ci/skills/assess-pr-risk/SKILL.md
  • plugins/ci/skills/assess-pr-test-results/SKILL.md
  • plugins/ci/skills/assess-pr-test-results/fetch_pr_test_results.py
  • plugins/ci/skills/fetch-jobs/SKILL.md
  • plugins/ci/skills/fetch-jobs/fetch_jobs.py
  • plugins/ci/skills/prow-job-cost-estimator/SKILL.md
  • plugins/ci/skills/prow-job-cost-estimator/estimate_cost.py

Comment thread plugins/ci/evals/eval-assess-pr-risk.yaml
Comment on lines +26 to +29
# Recent reverts in the same repo (last 6 months)
# Calculate the date 6 months ago as YYYY-MM-DD and use GitHub's merged: search qualifier
gh pr list --repo <org>/<repo> --search "revert in:title merged:>$(date -v-6m +%Y-%m-%d)" --state merged --limit 50 --json number,title,mergedAt
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file and surrounding lines
git ls-files plugins/ci/skills/assess-pr-risk/SKILL.md
wc -l plugins/ci/skills/assess-pr-risk/SKILL.md
cat -n plugins/ci/skills/assess-pr-risk/SKILL.md | sed -n '1,220p'

# Search for any other references to the same GH command pattern
rg -n "date -v-6m|--limit 50|gh pr list" plugins/ci/skills/assess-pr-risk/SKILL.md plugins -g '*.md'

Repository: openshift-eng/ai-helpers

Length of output: 19860


Make the historical cutoff portable and paginate all merged PRs.
date -v-6m is macOS-only, and the fixed --limit values here can silently truncate results on active repos. That undercounts reverts/merges and skews the historical-risk score. Use a portable cutoff and fetch all pages in both places.

🧰 Tools
🪛 SkillSpector (2.3.7)

[warning] 84: [MP2] Context Window Stuffing: Skill attempts to fill the context window with filler content, displacing legitimate instructions and safety constraints. This can degrade agent performance or bypass safety boundaries.

Remediation: Implement context-window management that detects and rejects padding or stuffing attempts. Prioritize system instructions over user-injected content.

(Memory Poisoning (MP2))


[warning] 86: [MP2] Context Window Stuffing: Skill attempts to fill the context window with filler content, displacing legitimate instructions and safety constraints. This can degrade agent performance or bypass safety boundaries.

Remediation: Implement context-window management that detects and rejects padding or stuffing attempts. Prioritize system instructions over user-injected content.

(Memory Poisoning (MP2))


[warning] 87: [MP2] Context Window Stuffing: Skill attempts to fill the context window with filler content, displacing legitimate instructions and safety constraints. This can degrade agent performance or bypass safety boundaries.

Remediation: Implement context-window management that detects and rejects padding or stuffing attempts. Prioritize system instructions over user-injected content.

(Memory Poisoning (MP2))


[warning] 88: [MP2] Context Window Stuffing: Skill attempts to fill the context window with filler content, displacing legitimate instructions and safety constraints. This can degrade agent performance or bypass safety boundaries.

Remediation: Implement context-window management that detects and rejects padding or stuffing attempts. Prioritize system instructions over user-injected content.

(Memory Poisoning (MP2))


[warning] 89: [MP2] Context Window Stuffing: Skill attempts to fill the context window with filler content, displacing legitimate instructions and safety constraints. This can degrade agent performance or bypass safety boundaries.

Remediation: Implement context-window management that detects and rejects padding or stuffing attempts. Prioritize system instructions over user-injected content.

(Memory Poisoning (MP2))


[warning] 90: [MP2] Context Window Stuffing: Skill attempts to fill the context window with filler content, displacing legitimate instructions and safety constraints. This can degrade agent performance or bypass safety boundaries.

Remediation: Implement context-window management that detects and rejects padding or stuffing attempts. Prioritize system instructions over user-injected content.

(Memory Poisoning (MP2))


[warning] 91: [MP2] Context Window Stuffing: Skill attempts to fill the context window with filler content, displacing legitimate instructions and safety constraints. This can degrade agent performance or bypass safety boundaries.

Remediation: Implement context-window management that detects and rejects padding or stuffing attempts. Prioritize system instructions over user-injected content.

(Memory Poisoning (MP2))


[warning] 92: [MP2] Context Window Stuffing: Skill attempts to fill the context window with filler content, displacing legitimate instructions and safety constraints. This can degrade agent performance or bypass safety boundaries.

Remediation: Implement context-window management that detects and rejects padding or stuffing attempts. Prioritize system instructions over user-injected content.

(Memory Poisoning (MP2))


[warning] 140: [MP2] Context Window Stuffing: Skill attempts to fill the context window with filler content, displacing legitimate instructions and safety constraints. This can degrade agent performance or bypass safety boundaries.

Remediation: Implement context-window management that detects and rejects padding or stuffing attempts. Prioritize system instructions over user-injected content.

(Memory Poisoning (MP2))

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/ci/skills/assess-pr-risk/SKILL.md` around lines 26 - 29, The
recent-reverts and merged-PR history commands in SKILL.md rely on a macOS-only
cutoff and fixed result limits, which can truncate data. Update the gh pr list
usage to use a portable 6-month date calculation and fetch all pages for both
the revert search and the merged PR query so assess-pr-risk gets complete
history. Keep the changes within the documented command snippets around gh pr
list and the merged: search qualifier.

Comment thread plugins/ci/skills/assess-pr-risk/SKILL.md
Comment thread plugins/ci/skills/prow-job-cost-estimator/estimate_cost.py
@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 11, 2026
Comment thread plugins/ci/skills/fetch-jobs/SKILL.md Outdated

This skill fetches job reports from the Sippy jobs API. It returns job metadata including pass rates, run counts, and trend data comparing the current 7-day window to the previous 7-day window. It supports both release jobs (e.g., `4.19`, `5.0`) and presubmit (pull request) jobs.

## When to Use This Skill

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

technically when to use doesn't have effect in body. It belongs to description or its own metadata https://code.claude.com/docs/en/skills.md

@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 20, 2026
dgoodwin and others added 3 commits July 20, 2026 08:44
The expected_tier is critical (71-100) but the score range started at 55,
which maps to the high tier. This internal inconsistency could pass the
score-range check while failing tier_matches_score.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
must_recommend_jobs was matching any substring in the full conversation,
so a job mentioned in a skip explanation could satisfy the check. Now it
only matches actual /test and /payload-job command lines, consistent with
how must_not_recommend_jobs already works.

Also adds a must_skip_jobs judge to enforce the skip contract declared
in the dataset schema but previously unchecked.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dgoodwin and others added 4 commits July 20, 2026 08:51
Unknown values were silently excluded from cost aggregations, which
could skew the PR risk report if upstream input was malformed. Now
raises ValueError on unexpected values so issues surface immediately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The "When to Use" section in the SKILL.md body has no effect on skill
triggering. Move the key use cases into the description field in the
frontmatter where the framework actually reads them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The parameter table documents the raw Sippy API defaults, but
fetch_jobs.py always sends its own values (current_runs/desc). Note
the distinction so users can predict the script's actual ordering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 21, 2026
@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 21, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/ci/evals/cases/assess-pr-risk/eval.yaml`:
- Around line 73-97: Update the state_file_valid check to require exactly one
JSON artifact instead of selecting the first from json_files. Expand top-level
and assessment validation to include every documented schema field, including
coverage_gaps, key_risks, cost_estimate, and notes, while preserving the
existing JSON parsing and failure reporting.
- Around line 140-168: Update the job_recommendations_correct check to validate
structured state only: compare must_recommend_jobs and must_not_recommend_jobs
against assessment.jobs_to_run, and compare must_skip_jobs against
assessment.jobs_to_skip. Remove conversation text from recommendation matching,
and report missing required skips alongside existing recommendation issues while
preserving the current success behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f0693fbe-881d-4e3e-9dfc-ec9d0f016ae4

📥 Commits

Reviewing files that changed from the base of the PR and between 6bd6fbd and c76b7ef.

📒 Files selected for processing (9)
  • docs/index.html
  • plugins/ci/evals/cases/assess-pr-risk/case-001-olm-hashstructure-upgrade/annotations.yaml
  • plugins/ci/evals/cases/assess-pr-risk/eval.md
  • plugins/ci/evals/cases/assess-pr-risk/eval.yaml
  • plugins/ci/evals/eval-assess-pr-risk.yaml
  • plugins/ci/skills/assess-pr-risk/SKILL.md
  • plugins/ci/skills/assess-pr-test-results/fetch_pr_test_results.py
  • plugins/ci/skills/fetch-jobs/SKILL.md
  • plugins/ci/skills/prow-job-cost-estimator/estimate_cost.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • plugins/ci/evals/cases/assess-pr-risk/case-001-olm-hashstructure-upgrade/annotations.yaml
  • plugins/ci/skills/prow-job-cost-estimator/estimate_cost.py
  • plugins/ci/skills/fetch-jobs/SKILL.md
  • plugins/ci/skills/assess-pr-test-results/fetch_pr_test_results.py
  • plugins/ci/evals/eval-assess-pr-risk.yaml
  • docs/index.html

Comment thread plugins/ci/evals/cases/assess-pr-risk/eval.yaml Outdated
@dgoodwin dgoodwin changed the title WIP: Pull Request Risk Analysis Agent Pull Request Risk Analysis Agent Jul 21, 2026
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 21, 2026
dgoodwin and others added 3 commits July 21, 2026 09:33
Require exactly one JSON artifact (not just pick the first) and validate
all documented assessment fields including coverage_gaps, key_risks,
cost_estimate, and notes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…text

The job_recommendations_correct judge was matching against the full
conversation text, causing false positives when job names appeared in
skip justifications. Now checks only assessment.jobs_to_run for
must_recommend and must_not_recommend, and validates must_skip_jobs
against assessment.jobs_to_skip.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
plugins/ci/skills/assess-pr-risk/SKILL.md (1)

28-28: ⚠️ Potential issue | 🟠 Major

Paginate historical PR queries instead of using fixed limits.

The portable date calculation is fixed, but --limit 50, --limit 100, and --limit 1000 still silently truncate results for active repositories. Count all matching pages before calculating revert rate and historical risk.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/ci/skills/assess-pr-risk/SKILL.md` at line 28, Update the historical
PR queries in plugins/ci/skills/assess-pr-risk/SKILL.md lines 28-28 and 110-113
to paginate through all matching pages instead of relying on fixed --limit
values of 50, 100, or 1000. Ensure the complete result set is counted before
calculating revert rates and historical risk.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/ci/evals/cases/assess-pr-risk/eval.md`:
- Around line 38-40: Update the test-case documentation to require that each
referenced GitHub PR is merged, matching the requirement in eval.yaml; keep the
existing accessibility and annotation requirements unchanged.

In `@plugins/ci/evals/cases/assess-pr-risk/eval.yaml`:
- Around line 40-47: Update the eval schema documentation and assessment flow to
add a structured assessment.hotspots field, then implement a deterministic
hotspots_correct judge with an explicit pass threshold that verifies every
expected_hotspots category. Apply the same changes to the related sections and
ensure the overall quality check includes this judge rather than relying only on
the LLM threshold.
- Around line 73-100: Update the state_file_valid check to require the sole JSON
file’s name to match <org>-<repo>-<pr_number>.json using the parsed data fields,
after validating the JSON and required fields. Reject any filename that does not
match this expected name while preserving the existing single-file and schema
validation behavior.

---

Duplicate comments:
In `@plugins/ci/skills/assess-pr-risk/SKILL.md`:
- Line 28: Update the historical PR queries in
plugins/ci/skills/assess-pr-risk/SKILL.md lines 28-28 and 110-113 to paginate
through all matching pages instead of relying on fixed --limit values of 50,
100, or 1000. Ensure the complete result set is counted before calculating
revert rates and historical risk.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5d72f2e6-b181-4cd6-8392-d72c996a6e58

📥 Commits

Reviewing files that changed from the base of the PR and between 6bd6fbd and 7888aeb.

📒 Files selected for processing (10)
  • docs/index.html
  • plugins/ci/.claude-plugin/plugin.json
  • plugins/ci/evals/cases/assess-pr-risk/case-001-olm-hashstructure-upgrade/annotations.yaml
  • plugins/ci/evals/cases/assess-pr-risk/eval.md
  • plugins/ci/evals/cases/assess-pr-risk/eval.yaml
  • plugins/ci/evals/eval-assess-pr-risk.yaml
  • plugins/ci/skills/assess-pr-risk/SKILL.md
  • plugins/ci/skills/assess-pr-test-results/fetch_pr_test_results.py
  • plugins/ci/skills/fetch-jobs/SKILL.md
  • plugins/ci/skills/prow-job-cost-estimator/estimate_cost.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • plugins/ci/.claude-plugin/plugin.json
  • plugins/ci/evals/cases/assess-pr-risk/case-001-olm-hashstructure-upgrade/annotations.yaml
  • docs/index.html
  • plugins/ci/evals/eval-assess-pr-risk.yaml
  • plugins/ci/skills/assess-pr-test-results/fetch_pr_test_results.py
  • plugins/ci/skills/prow-job-cost-estimator/estimate_cost.py
  • plugins/ci/skills/fetch-jobs/SKILL.md

Comment on lines +38 to +40
Each test case has:
- `input.yaml`: Single field `pr_url` — a real GitHub PR URL (e.g., `https://github.com/openshift/origin/pull/31350`). The PR must exist and be accessible via `gh`.
- `annotations.yaml`: Expected outcomes for scoring — `expected_tier`, `expected_score_range` (min/max), `must_recommend_jobs`, `must_not_recommend_jobs`, `must_skip_jobs`, `expected_hotspots`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require merged PRs here as well.

This permits open PRs, while eval.yaml requires merged PRs. Mutable inputs can invalidate fixed score and job annotations; document the same merged-PR requirement.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/ci/evals/cases/assess-pr-risk/eval.md` around lines 38 - 40, Update
the test-case documentation to require that each referenced GitHub PR is merged,
matching the requirement in eval.yaml; keep the existing accessibility and
annotation requirements unchanged.

Comment on lines +40 to +47
- annotations.yaml: Expected outcomes for scoring, with fields:
'description' (what the PR does and why it's a good test case),
'expected_tier' (low|medium|high|critical),
'expected_score_range' (min/max integers 0-100),
'must_recommend_jobs' (list of job name substrings that must appear in recommendations),
'must_not_recommend_jobs' (list of job name substrings that must NOT appear),
'must_skip_jobs' (list of jobs that must be explicitly skipped),
'expected_hotspots' (list of hotspot categories the skill should flag).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Make expected hotspots deterministically testable.

expected_hotspots has no corresponding structured state field or deterministic judge. The LLM quality threshold can pass without verifying every expected category. Add a documented assessment.hotspots field and a hotspots_correct judge with a threshold.

Also applies to: 52-59, 181-230

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/ci/evals/cases/assess-pr-risk/eval.yaml` around lines 40 - 47, Update
the eval schema documentation and assessment flow to add a structured
assessment.hotspots field, then implement a deterministic hotspots_correct judge
with an explicit pass threshold that verifies every expected_hotspots category.
Apply the same changes to the related sections and ensure the overall quality
check includes this judge rather than relying only on the LLM threshold.

Comment on lines +73 to +100
- name: state_file_valid
description: |
Checks that the skill produced exactly one valid JSON state file in
.work/pr-risk/ with all required top-level and assessment fields.
check: |
import json
files = outputs.get("files", {})
json_files = {k: v for k, v in files.items() if k.endswith(".json")}
if not json_files:
return (False, "No JSON state file found in .work/pr-risk/")
if len(json_files) > 1:
return (False, f"Expected 1 JSON file, found {len(json_files)}: {list(json_files.keys())}")
fname, content = next(iter(json_files.items()))
try:
data = json.loads(content)
except json.JSONDecodeError as e:
return (False, f"Invalid JSON: {e}")
required = ["pr_url", "org", "repo", "pr_number", "assessment"]
missing = [f for f in required if f not in data]
if missing:
return (False, f"Missing top-level fields: {missing}")
assess = data["assessment"]
a_required = ["risk_score", "risk_tier", "risk_breakdown", "jobs_to_run",
"jobs_to_skip", "coverage_gaps", "key_risks", "cost_estimate", "notes"]
a_missing = [f for f in a_required if f not in assess]
if a_missing:
return (False, f"Missing assessment fields: {a_missing}")
return (True, f"Valid state file: {fname}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate the documented state-file name.

The check accepts any single JSON filename, including stale or unrelated artifacts. Require <org>-<repo>-<pr_number>.json after parsing the state data.

Proposed fix
       if missing:
           return (False, f"Missing top-level fields: {missing}")
+      expected_name = f"{data['org']}-{data['repo']}-{data['pr_number']}.json"
+      actual_name = fname.rsplit("/", 1)[-1]
+      if actual_name != expected_name:
+          return (False, f"Expected state file '{expected_name}', got '{actual_name}'")
       assess = data["assessment"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: state_file_valid
description: |
Checks that the skill produced exactly one valid JSON state file in
.work/pr-risk/ with all required top-level and assessment fields.
check: |
import json
files = outputs.get("files", {})
json_files = {k: v for k, v in files.items() if k.endswith(".json")}
if not json_files:
return (False, "No JSON state file found in .work/pr-risk/")
if len(json_files) > 1:
return (False, f"Expected 1 JSON file, found {len(json_files)}: {list(json_files.keys())}")
fname, content = next(iter(json_files.items()))
try:
data = json.loads(content)
except json.JSONDecodeError as e:
return (False, f"Invalid JSON: {e}")
required = ["pr_url", "org", "repo", "pr_number", "assessment"]
missing = [f for f in required if f not in data]
if missing:
return (False, f"Missing top-level fields: {missing}")
assess = data["assessment"]
a_required = ["risk_score", "risk_tier", "risk_breakdown", "jobs_to_run",
"jobs_to_skip", "coverage_gaps", "key_risks", "cost_estimate", "notes"]
a_missing = [f for f in a_required if f not in assess]
if a_missing:
return (False, f"Missing assessment fields: {a_missing}")
return (True, f"Valid state file: {fname}")
- name: state_file_valid
description: |
Checks that the skill produced exactly one valid JSON state file in
.work/pr-risk/ with all required top-level and assessment fields.
check: |
import json
files = outputs.get("files", {})
json_files = {k: v for k, v in files.items() if k.endswith(".json")}
if not json_files:
return (False, "No JSON state file found in .work/pr-risk/")
if len(json_files) > 1:
return (False, f"Expected 1 JSON file, found {len(json_files)}: {list(json_files.keys())}")
fname, content = next(iter(json_files.items()))
try:
data = json.loads(content)
except json.JSONDecodeError as e:
return (False, f"Invalid JSON: {e}")
required = ["pr_url", "org", "repo", "pr_number", "assessment"]
missing = [f for f in required if f not in data]
if missing:
return (False, f"Missing top-level fields: {missing}")
expected_name = f"{data['org']}-{data['repo']}-{data['pr_number']}.json"
actual_name = fname.rsplit("/", 1)[-1]
if actual_name != expected_name:
return (False, f"Expected state file '{expected_name}', got '{actual_name}'")
assess = data["assessment"]
a_required = ["risk_score", "risk_tier", "risk_breakdown", "jobs_to_run",
"jobs_to_skip", "coverage_gaps", "key_risks", "cost_estimate", "notes"]
a_missing = [f for f in a_required if f not in assess]
if a_missing:
return (False, f"Missing assessment fields: {a_missing}")
return (True, f"Valid state file: {fname}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/ci/evals/cases/assess-pr-risk/eval.yaml` around lines 73 - 100,
Update the state_file_valid check to require the sole JSON file’s name to match
<org>-<repo>-<pr_number>.json using the parsed data fields, after validating the
JSON and required fields. Reject any filename that does not match this expected
name while preserving the existing single-file and schema validation behavior.

import urllib.request
from datetime import datetime, timedelta, timezone

SIPPY_API_BASE = "http://localhost:8080/api"

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.

Needs to be updated?

@@ -0,0 +1,278 @@
#!/usr/bin/env python3
"""Fetch test results for a pull request from the Sippy API.

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.

The data lag on sippy is sort of unfortunate, I have mostly stopped using it in AI skills since they're perfectly capable of just grabbing the data from the PR itself (both the entire test matrix for the given sha via https://prow.ci.openshift.org/pr-history/?org=openshift&repo=origin&pr=31404, and payload jobs in comments)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That is surprising, do you think that's the route this should go? I'm nervous about /payload, possibly with aggregated jobs, and flake vs fail parsing, test names, other things that might go wrong. It seems like it might be best to rely on the formalized parsing. It does however lag and requires a PR in sippy that's still outstanding.

@stbenjam stbenjam Jul 21, 2026

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.

The main deciding factor is how you want to invoke these skills. If I were building this, I would want these skills invoked by an analysis presubmit job (either always run, or invoked ad hoc) on a PR that:

  • Waits for all presubmits and /payload jobs on a PR to finish
  • Analyzes the results with this skill
  • Leaves a comment on the PR (eventually allowing the job to inform a merge decision - possibly overriding jobs, applying a label, or deciding more tests should run on the PR)

It can still be formalized with a python script. You could also use Sippy API, but against live data (bigquery? but I guess there's cost problems...)

The delay in Sippy’s data makes that not really feasible. We've had cases recently where TRT went to 4+ hours for sync time (but I think its basically back to an hour now)

gh pr view <url> --json number,title,body,author,files,labels,state,additions,deletions,changedFiles,baseRefName,headRefName,statusCheckRollup

# Diff summary (always fetch this first for large PRs)
gh pr diff <url> --stat

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not finding a --stat option, here and line 31

gh pr diff <url>

# PR comments — look for reviewer feedback and CodeRabbit analysis
gh api repos/<org>/<repo>/pulls/<pr_number>/comments --paginate --jq '.[] | {user: .user.login, body: .body}' 2>/dev/null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think gh pr view <pr number> --comments might be a lot more efficient

branch="<baseRefName from PR metadata>"

# Fetch the CI config YAML
gh api repos/openshift/release/contents/ci-operator/config/<org>/<repo>/<org>-<repo>-${branch}.yaml --jq '.content' | base64 -d > /tmp/ci-config.yaml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if the config file doesn't exist the empty file that gets created seems like it will break the python down below. need a check in that that file is non-empty

@bentito

bentito commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

I think the skills look pretty good! I only had time to just comment on some practical considerations on the risk assessment skill

Examine the diff for these patterns. Points are additive but capped at 30:

| Factor | Points | What to Look For |
| ---------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------- |

@Prashanth684 Prashanth684 Jul 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

these factors seem very specific to operator repos. There are some repos like ironic-agent-image, ovn-kubernetes for example where many of the categories will never be met and this value becomes inaccurate - should we have some generic factors that can cover non operator/controller repos as well?

@not-stbenjam

Copy link
Copy Markdown
Contributor

Deep Review Verdict

Disposition: REQUEST_CHANGES — 4 confirmed bugs, all verified with runtime reproducers


Specialist Findings

Bugs (2 findings: 1 blocking, 1 suggestion)
# Severity File Finding
1 BLOCKING fetch_pr_test_results.py:16 Hardcoded localhost:8080 URL — script non-functional
2 SUGGESTION fetch_jobs.py:138 format_summary TypeError on API null values
Adversarial (13 findings: 4 blocking, 5 suggestions, 4 notes)
# Severity File Finding
1 BLOCKING fetch_jobs.py:138 format_summary TypeError on null fields
2 BLOCKING fetch_jobs.py:35 get_latest_release assumes sorted API response
3 BLOCKING fetch_pr_test_results.py:16 localhost:8080 URL — connection refused
4 BLOCKING fetch_pr_test_results.py:127 format_summary KeyError on missing dict keys
5 SUGGESTION fetch_pr_test_results.py:226 Malformed URLs produce garbage org/repo
6 SUGGESTION estimate_cost.py:39 estimate() mutates input in-place
7 SUGGESTION estimate_cost.py:47 Missing validation for name/duration_minutes
8 SUGGESTION estimate_cost.py:50 Negative duration_minutes produces negative costs
9 SUGGESTION estimate_cost.py:27 Platform detection order-dependent substring matching
10 SUGGESTION SKILL.md:69 Section B scoring can exceed stated 0-25 max
11 NOTE SKILL.md:69 File count tier boundary overlap at 30
12 NOTE SKILL.md:110 Revert rate inflated by --limit caps on active repos
13 NOTE fetch_jobs.py:29 get_latest_release crashes if API returns null releases
Security & Supply Chain (7 findings, no blocking)
# Severity File Finding
1 SUGGESTION fetch_pr_test_results.py:16 Plaintext HTTP for Sippy API
2 SUGGESTION SKILL.md:157 Unsanitized PR metadata in shell commands
3 SUGGESTION eval.yaml:21 Overly broad Bash permission in eval config
4-7 NOTE various API URL in errors, arbitrary file read, yaml.safe_load on remote content, eval judge safety
Architecture (8 findings: 1 blocking, 4 suggestions, 3 notes)
# Severity File Finding
1 BLOCKING fetch_pr_test_results.py:16 SIPPY_API_BASE localhost vs production
2 SUGGESTION eval.yaml:1 Duplicate divergent eval definitions
3 SUGGESTION eval.yaml:67 Critical eval judges dropped without replacement
4 SUGGESTION pr-risk-analyst.md:16 Agent claims test skill is "planned" but it ships now
5 SUGGESTION eval.yaml:21 Blanket Bash permission less safe than granular
6-8 NOTE various Input mutation, agent color format, unusual query string construction
Codebase Consistency (7 findings: 1 blocking, 3 suggestions, 3 notes)
# Severity File Finding
1 BLOCKING fetch_pr_test_results.py:16 localhost URL inconsistent with all other scripts
2 SUGGESTION eval-assess-pr-risk.yaml:3 Skill ref missing ci: prefix
3 SUGGESTION eval.yaml:1 Duplicate eval config in two locations
4 SUGGESTION pr-risk-analyst.md:18 Agent text contradicts co-shipped skill
5-7 NOTE various Missing See Also section, duplicated get_latest_release, permission style inconsistency
QA Engineer (12 findings, no blocking)
# Severity File Finding
1-3 SUGGESTION estimate_cost.py, fetch_jobs.py, fetch_pr_test_results.py No unit tests for any Python script
4 SUGGESTION annotations Overly broad 'e2e' substring in eval must_not_recommend
5 SUGGESTION annotations Case 001 missing must_skip_jobs entries
6 SUGGESTION evals Only 3 eval cases, missing high-risk tier coverage
7-12 NOTE various Duplicate eval, sys.exit vs exceptions, dict access inconsistency, negative duration, input mutation
Technical Writer (3 findings: 1 blocking, 1 suggestion, 1 note)
# Severity File Finding
1 BLOCKING fetch_pr_test_results.py:16 Script hardcodes localhost but SKILL.md implies public API
2 SUGGESTION pr-risk-analyst.md:18 Agent says skill is "planned" but it ships in this PR
3 NOTE SKILL.md:21 Prerequisites don't document localhost requirement

Panel Synthesis

Strong consensus across all 7 specialists on the localhost URL issue — every reviewer independently flagged it. fetch_pr_test_results.py is the only script in 15+ Sippy clients that uses localhost:8080 instead of the production URL. Reproducer confirmed connection refused, and verified the same query succeeds against the public endpoint with 277 results.

Three additional bugs were confirmed by reproducers:

  1. format_summary TypeError (fetch_jobs.py:138) — .get('field', 0) does not protect against JSON null values where the key exists with None. The author already handles this correctly for duration_minutes at line 157 with an explicit is not None check, but missed the same pattern for pass percentages. Fix: use (j.get('field') or 0).

  2. get_latest_release unsorted assumption (fetch_jobs.py:35) — Returns ocp_releases[0] without sorting. Currently works because the Sippy API happens to return descending order, but no sort is applied. Reproducer showed wrong results with non-descending input. Fix: add sorted(..., reverse=True) with version-aware key.

  3. format_summary KeyError (fetch_pr_test_results.py:127) — Direct dict access (r['prow_job_run_id'], r['prow_job_name'], f['test_name']) crashes on missing keys, while other fields in the same function correctly use .get(). All three crash paths confirmed.


Required Actions Before Merge

  1. Fix SIPPY_API_BASE in fetch_pr_test_results.py:16 — Change from http://localhost:8080/api to https://sippy.dptools.openshift.org/api.

  2. Fix null handling in fetch_jobs.py:138,148,166 — Use (j.get('current_pass_percentage') or 0) instead of j.get('current_pass_percentage', 0) for all float-formatted fields.

  3. Sort releases in fetch_jobs.py:35 — Add version-aware sort before taking [0].

  4. Fix KeyError risk in fetch_pr_test_results.py:127,162 — Use .get() with defaults for prow_job_run_id, prow_job_name, and test_name.


Optional Follow-ups

  • Update pr-risk-analyst.md:18 to reference ci:assess-pr-test-results instead of saying it's "planned for a future iteration" (3 specialists flagged)
  • Consolidate the two eval.yaml files — decide which is canonical and remove the other (3 specialists flagged)
  • Fix ci: prefix in eval-assess-pr-risk.yaml:3 skill reference
  • Add explicit cap to Section B scoring in SKILL.md:69 ("capped at 25")
  • Add unit tests for the three Python scripts
  • Add more eval cases to cover high-risk tier and docs-only PRs

Stats: 52 raw findings from 7 specialists. Kept: 4 blocking (4 reproduced), 16 suggestions, 14 notes. Dropped: 18 duplicates.

Generated by /deep-review

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 28, 2026
@openshift-ci

openshift-ci Bot commented Jul 28, 2026

Copy link
Copy Markdown

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@dgoodwin dgoodwin changed the title Pull Request Risk Analysis Agent OCPQUAL-30: Pull Request Risk Analysis Agent Jul 28, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 28, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@dgoodwin: This pull request references OCPQUAL-30 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the epic to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Adds two new skills to be part of a future PR pipeline.

assess-pr-risk attempts to determine a risk level for a PR (given code complexity, historical revert risk of the repo), determine what testing should be run against it. The goal here is to improve quality and pre-merge issue detection, while also reducing cost by skipping jobs that are not required. The skill works with the current set of presubmits, and a small set of optional /payload jobs it knows about. If successful, the savings on not running unnecessary jobs may outweigh the cost of performing the analysis on all PRs. The plugin makes a rough guess on how much it spent/saved on CI testing.

assess-pr-test-results attempts to determine if the testing results (across presubmits and /payload jobs) look suspicious, or if we are seeing any failures that look suspicious.

Includes a start on a few evals: /eval-run plugins/ci/evals/cases/assess-pr-risk/

Summary by CodeRabbit

  • New Features
  • Added PR risk assessment with risk scoring, hotspot-aware CI job run/skip recommendations, and cost estimates.
  • Introduced CI tools to fetch PR test results, fetch CI jobs, and estimate Prow job costs.
  • Added a PR risk analyst agent and expanded the marketplace catalog (including standalone skills).
  • Added assess-pr-risk evaluation definitions and new scenarios for risk scoring/job recommendations.
  • Documentation
  • Added/updated skill docs for PR risk assessment, PR test results analysis, job retrieval, and Prow cost estimation.
  • Chores
  • Updated .gitignore to exclude evaluation run outputs.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants