Skip to content

feat(ci): add payload-blocking-runs and kubelet-version-check skills - #616

Open
redhat-chai-bot wants to merge 1 commit into
openshift-eng:mainfrom
redhat-chai-bot:ci-payload-blocking-tools
Open

feat(ci): add payload-blocking-runs and kubelet-version-check skills#616
redhat-chai-bot wants to merge 1 commit into
openshift-eng:mainfrom
redhat-chai-bot:ci-payload-blocking-tools

Conversation

@redhat-chai-bot

@redhat-chai-bot redhat-chai-bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds two new skills (with scripts) and two new commands to the ci plugin to support analyzing blocking /payload job runs against a PR and verifying which kubelet version they ran with.

These compose into a pipeline for questions like: "For blocking jobs running against a rebase PR since , did they succeed and run with the rebased kubelet?"

What's added

Skill: payload-blocking-runs

payload_blocking_runs.py queries BigQuery (openshift-gce-devel.ci_analysis_us.jobs) for /payload job runs against a given org/repo/PR over a time window, then filters to the blocking set by fetching results.blockingJobs from the release controller API for the specified release streams and matching test-name suffixes. Emits sorted gcsweb artifact URLs.

  • Flags: --org --repo --pr --start --end --streams --all-jobs --show-jobs -v -o
  • --verbose surfaces which suffixes were extracted and which jobs did/didn't match — the recommended way to validate filter accuracy when the blocking set changes over time
  • Deps: google-cloud-bigquery, requests; BigQuery ADC auth

Skill: kubelet-version-check

get_kubestat.py takes a text file of gcsweb URLs (one per line) and, for each run, reads finished.json (status) and the gather-extra/artifacts/oc_cmds/nodes artifact (kubelet version), emitting a table of job / build_id / status / kubelet_version. Stdlib-only, 24-worker threadpool, optional GOOGLE_APPLICATION_CREDENTIALS.

Commands

  • /ci:list-payload-blocking-runs <org/repo#pr> [--since] [--streams ...] — runs the skill and adds AI reasoning: coverage-gap analysis (blocking jobs with zero runs) and next steps
  • /ci:check-kubelet-versions <payload-runs.txt> — runs the skill and adds AI reasoning: kubelet-version consistency, flags unexpected/missing versions, correlates version with pass/fail

Housekeeping

  • Bumped plugins/ci/.claude-plugin/plugin.json 0.0.680.0.69
  • make update synced marketplace.json and regenerated docs
  • make lint passes (Grade: A+, 0 errors/warnings); both scripts pass py_compile

Notes for reviewers

  • The payload_blocking_runs.py script was reconstructed from a working ad-hoc version; please sanity-check the BQ query and suffix-derivation logic against your expectations.
  • Auth is currently split: the BQ script uses gcloud ADC, get_kubestat.py uses optional service-account JSON. Harmonizing these could be a nice follow-up.

Summary by CodeRabbit

  • New Features
    • Added tools to find release-blocking /payload test runs for a pull request.
    • Added kubelet version analysis for test runs, including status reporting and anomaly detection.
    • Added support for filtering runs by release stream, date range, and job coverage.
  • Documentation
    • Added usage guides, prerequisites, examples, and troubleshooting details for the new CI commands and skills.
  • Chores
    • Updated the CI plugin version to 0.0.69.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@openshift-ci

openshift-ci Bot commented Jul 10, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: redhat-chai-bot
Once this PR has been reviewed and has the lgtm label, please assign stleerh for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found 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 requested review from dgoodwin and mrunalp July 10, 2026 18:40
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds two CI workflows: one lists release-blocking payload runs using BigQuery and release-controller data, and the other extracts kubelet versions and statuses from GCS artifacts. New commands, skills, catalog entries, and plugin version metadata document and register the workflows.

Changes

CI payload analysis

Layer / File(s) Summary
Payload blocking-run discovery
plugins/ci/skills/payload-blocking-runs/payload_blocking_runs.py
Queries payload runs, filters them against release-controller blocking jobs, and outputs deduplicated gcsweb URLs.
Kubelet version analysis
plugins/ci/skills/kubelet-version-check/get_kubestat.py
Concurrently reads GCS artifacts to extract job status and kubelet versions, then prints a sorted table.
Command and skill documentation
plugins/ci/commands/*, plugins/ci/skills/*/SKILL.md
Documents command arguments, execution flows, filtering, outputs, prerequisites, errors, and examples.
Plugin catalog and version metadata
.claude-plugin/marketplace.json, plugins/ci/.claude-plugin/plugin.json, docs/index.html
Registers the new commands and skills and updates the CI plugin version to 0.0.69.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant payload_blocking_runs.py
  participant BigQuery
  participant release-controller
  User->>payload_blocking_runs.py: provide PR and stream options
  payload_blocking_runs.py->>BigQuery: query payload runs
  payload_blocking_runs.py->>release-controller: retrieve blocking jobs
  payload_blocking_runs.py-->>User: output matching gcsweb URLs
Loading
sequenceDiagram
  participant User
  participant get_kubestat.py
  participant GCS
  User->>get_kubestat.py: provide payload-run URL list
  get_kubestat.py->>GCS: fetch finished.json and node artifacts
  GCS-->>get_kubestat.py: return status and kubelet data
  get_kubestat.py-->>User: output sorted analysis table
Loading

Possibly related PRs

Suggested reviewers: cblecker, rvanderp3

🚥 Pre-merge checks | ✅ 10
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change by naming the two new CI skills added in this PR.
Docstring Coverage ✅ Passed Docstring coverage is 84.21% which is sufficient. The required threshold is 80.00%.
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 changed command/skill docs use real-person names as style refs or examples; only product, job, and repo names appear.
No Assumed Git Remote Names ✅ Passed No new CI doc/script hardcodes git remotes; searches found no git fetch/push/remote usage, and 'origin' only appears as a repo slug example.
Git Push Safety Rules ✅ Passed The PR’s changed files contain no git push, --force, or force-with-lease references, so it doesn’t violate the push safety rules.
No Untrusted Mcp Servers ✅ Passed Changed files add CI docs/scripts and version bumps only; no new MCP server installs, npx mcp-server packages, or untrusted repo sources appear.
Ai-Helpers Overlap Detection ✅ Passed No existing ai-helper file closely matches these new payload/blocking and kubelet-version helpers; nearest payload-analysis/fetch-test-runs docs are far below the 60% threshold.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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: 11

🧹 Nitpick comments (2)
plugins/ci/skills/kubelet-version-check/get_kubestat.py (2)

42-44: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Redundant token fetches under concurrency (optional).

With 24 worker threads, the first wave all observe an empty _token_cache and each mints a JWT + performs a token exchange (~24 calls instead of 1). Correctness is unaffected (GIL keeps the dict updates atomic), but a threading.Lock around the mint-and-cache path would avoid the thundering herd.

🤖 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/kubelet-version-check/get_kubestat.py` around lines 42 -
44, Add a module-level threading.Lock and guard the token minting and
cache-update path in the token-fetch function, rechecking _token_cache after
acquiring the lock before performing the exchange. Keep the existing fast path
outside the lock, and ensure the lock covers only the refresh logic to prevent
redundant concurrent token fetches.

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

Hardcoded version regex is brittle (optional).

_VERSION_RE only recognizes v1.35/v1.36; any other kubelet version is reported as unexpected: <version> rather than the value itself. If this tool should track future releases, consider a general v\d+\.\d+\.\S+ match (with the acceptable range enforced separately) so new versions aren't mislabeled.

🤖 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/kubelet-version-check/get_kubestat.py` at line 180, Update
_VERSION_RE to recognize kubelet versions generically using a pattern such as
v\d+\.\d+\.\S+, and keep supported-version validation separate so future
versions are reported accurately rather than as unexpected values.
🤖 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/commands/check-kubelet-versions.md`:
- Line 53: Update the command invoking the kubelet check script to pass
payload-runs.txt as a positional argument instead of shell input redirection, so
get_kubestat.py receives the filename through sys.argv[1].
- Around line 80-89: Add language identifiers to every fenced code block in the
documented command examples, using ```text for plain commands and ```bash where
shell syntax is intended, including the examples for /ci:check-kubelet-versions
and the end-to-end command sequence.
- Around line 47-51: Remove the fallback search under ~/.claude/plugins in the
kubelet version check command. Require KUBELET_CHECK to resolve exclusively from
CLAUDE_PLUGIN_ROOT, and fail closed with the existing error when that path is
unavailable or the script is missing; do not execute any discovered alternative.

In `@plugins/ci/commands/list-payload-blocking-runs.md`:
- Around line 54-58: Replace the angle-bracket placeholders in the executable
command with properly quoted shell variables or arrays for the organization,
repository, PR, and streams arguments; alternatively, explicitly label the
snippet as pseudocode requiring substitution before execution.
- Around line 92-99: Add language identifiers to the fenced command examples in
the documentation, using ```text for plain command invocations and ```bash where
shell syntax is intended; update both examples under the default and explicit
lookback sections.
- Around line 48-52: The fallback in the payload-blocking script path resolution
must not discover or execute scripts from ~/.claude/plugins. Remove the
find-based fallback and fail closed when CLAUDE_PLUGIN_ROOT does not contain
payload_blocking_runs.py, or require an explicitly supplied trusted path before
proceeding; preserve the existing missing-script error handling.

In `@plugins/ci/skills/kubelet-version-check/get_kubestat.py`:
- Around line 272-274: Update the width calculation in get_kubestat.py so it
handles an empty results collection without calling max() with only a single
integer. In the table-formatting logic around widths, compute each column width
from the header plus any available row lengths, preserving header-only widths
when results is empty so the tool can print an empty table.
- Around line 263-267: Handle exceptions from each future independently in the
loop consuming `as_completed(futures)`: wrap `fut.result()` in a try/except,
catch the parsing failure such as `ValueError`, and append an error row matching
the existing result schema instead of aborting. Preserve progress reporting and
continue processing all remaining URLs.

In `@plugins/ci/skills/kubelet-version-check/SKILL.md`:
- Around line 49-53: Remove the ~/.claude/plugins fallback from the
KUBELET_CHECK initialization. Use only the script under CLAUDE_PLUGIN_ROOT (or
another explicitly trusted fixed path), then retain the existence check and
error exit without searching or executing files from the agent configuration
directory.
- Around line 96-104: The documented per-run error handling does not match the
implementation because ValueError from parse_url() propagates through main() via
fut.result(). Update the script so main() catches per-run parse_url() failures
and emits an inline error row while continuing other runs, then keep the Error
Handling documentation aligned with that behavior.

In `@plugins/ci/skills/payload-blocking-runs/SKILL.md`:
- Around line 46-48: Remove the ~/.claude/plugins fallback lookup from the
PAYLOAD_BLOCKING initialization. Resolve the script only through
CLAUDE_PLUGIN_ROOT or another explicitly trusted path, and retain the validation
that fails closed when no valid payload_blocking_runs.py is found.

---

Nitpick comments:
In `@plugins/ci/skills/kubelet-version-check/get_kubestat.py`:
- Around line 42-44: Add a module-level threading.Lock and guard the token
minting and cache-update path in the token-fetch function, rechecking
_token_cache after acquiring the lock before performing the exchange. Keep the
existing fast path outside the lock, and ensure the lock covers only the refresh
logic to prevent redundant concurrent token fetches.
- Line 180: Update _VERSION_RE to recognize kubelet versions generically using a
pattern such as v\d+\.\d+\.\S+, and keep supported-version validation separate
so future versions are reported accurately rather than as unexpected values.
🪄 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: 7910e1d7-246d-467a-a9e4-cd6368acd0bc

📥 Commits

Reviewing files that changed from the base of the PR and between 2a83f75 and d249608.

📒 Files selected for processing (9)
  • .claude-plugin/marketplace.json
  • docs/index.html
  • plugins/ci/.claude-plugin/plugin.json
  • plugins/ci/commands/check-kubelet-versions.md
  • plugins/ci/commands/list-payload-blocking-runs.md
  • plugins/ci/skills/kubelet-version-check/SKILL.md
  • plugins/ci/skills/kubelet-version-check/get_kubestat.py
  • plugins/ci/skills/payload-blocking-runs/SKILL.md
  • plugins/ci/skills/payload-blocking-runs/payload_blocking_runs.py

Comment on lines +47 to +51
KUBELET_CHECK="${CLAUDE_PLUGIN_ROOT}/skills/kubelet-version-check/get_kubestat.py"
if [ ! -f "$KUBELET_CHECK" ]; then
KUBELET_CHECK=$(find ~/.claude/plugins -type f -path "*/ci/skills/kubelet-version-check/get_kubestat.py" 2>/dev/null | sort | head -1)
fi
if [ -z "$KUBELET_CHECK" ] || [ ! -f "$KUBELET_CHECK" ]; then echo "ERROR: get_kubestat.py not found" >&2; exit 2; fi

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not execute scripts discovered under ~/.claude/plugins.

This fallback selects and executes the first matching script from a mutable agent configuration directory, which could run stale or attacker-controlled code with the user’s credentials. Fail closed when CLAUDE_PLUGIN_ROOT is unavailable, or require an explicitly supplied trusted path.

🤖 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/commands/check-kubelet-versions.md` around lines 47 - 51, Remove
the fallback search under ~/.claude/plugins in the kubelet version check
command. Require KUBELET_CHECK to resolve exclusively from CLAUDE_PLUGIN_ROOT,
and fail closed with the existing error when that path is unavailable or the
script is missing; do not execute any discovered alternative.

Source: Linters/SAST tools

fi
if [ -z "$KUBELET_CHECK" ] || [ ! -f "$KUBELET_CHECK" ]; then echo "ERROR: get_kubestat.py not found" >&2; exit 2; fi

python3 "$KUBELET_CHECK" <payload-runs.txt>

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 | 🔴 Critical | ⚡ Quick win

Pass the filename as an argument, not shell redirection.

<payload-runs.txt> is parsed as shell redirection, and get_kubestat.py expects the path in sys.argv[1]; as written, this command cannot run. Use python3 "$KUBELET_CHECK" payload-runs.txt.

🤖 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/commands/check-kubelet-versions.md` at line 53, Update the command
invoking the kubelet check script to pass payload-runs.txt as a positional
argument instead of shell input redirection, so get_kubestat.py receives the
filename through sys.argv[1].

Comment on lines +80 to +89
```
/ci:check-kubelet-versions payload-runs.txt
```

2. **End-to-end from a PR** (run the two commands in sequence):
```
/ci:list-payload-blocking-runs openshift/machine-config-operator#5509 --streams 4.20.0-0.nightly
# save the emitted URLs to payload-runs.txt, then:
/ci:check-kubelet-versions payload-runs.txt
```

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add language identifiers to fenced code blocks.

Use text for command examples or bash where shell syntax is intended.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 80-80: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 85-85: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 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/commands/check-kubelet-versions.md` around lines 80 - 89, Add
language identifiers to every fenced code block in the documented command
examples, using ```text for plain commands and ```bash where shell syntax is
intended, including the examples for /ci:check-kubelet-versions and the
end-to-end command sequence.

Source: Linters/SAST tools

Comment on lines +48 to +52
PAYLOAD_BLOCKING="${CLAUDE_PLUGIN_ROOT}/skills/payload-blocking-runs/payload_blocking_runs.py"
if [ ! -f "$PAYLOAD_BLOCKING" ]; then
PAYLOAD_BLOCKING=$(find ~/.claude/plugins -type f -path "*/ci/skills/payload-blocking-runs/payload_blocking_runs.py" 2>/dev/null | sort | head -1)
fi
if [ -z "$PAYLOAD_BLOCKING" ] || [ ! -f "$PAYLOAD_BLOCKING" ]; then echo "ERROR: payload_blocking_runs.py not found" >&2; exit 2; fi

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not execute scripts discovered under ~/.claude/plugins.

The fallback executes the first matching script from a mutable agent configuration directory, allowing stale or attacker-controlled code to run with the user’s credentials. Fail closed or require an explicitly supplied trusted script path.

🤖 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/commands/list-payload-blocking-runs.md` around lines 48 - 52, The
fallback in the payload-blocking script path resolution must not discover or
execute scripts from ~/.claude/plugins. Remove the find-based fallback and fail
closed when CLAUDE_PLUGIN_ROOT does not contain payload_blocking_runs.py, or
require an explicitly supplied trusted path before proceeding; preserve the
existing missing-script error handling.

Comment on lines +54 to +58
python3 "$PAYLOAD_BLOCKING" \
--org <org> --repo <repo> --pr <pr> \
--start <since> \
--streams <stream> [<stream> ...] \
--verbose --show-jobs

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 | 🔴 Critical | ⚡ Quick win

Do not use angle-bracket placeholders in executable shell.

--org <org> --repo <repo> --pr <pr> and --streams <stream> [<stream> ...] are interpreted as shell redirections, not arguments. Use quoted shell variables/arrays, or clearly mark this as pseudocode that must be substituted before execution.

🤖 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/commands/list-payload-blocking-runs.md` around lines 54 - 58,
Replace the angle-bracket placeholders in the executable command with properly
quoted shell variables or arrays for the organization, repository, PR, and
streams arguments; alternatively, explicitly label the snippet as pseudocode
requiring substitution before execution.

Comment on lines +263 to +267
for fut in as_completed(futures):
done += 1
if done % 50 == 0 or done == len(urls):
print(f" {done}/{len(urls)} done", file=sys.stderr)
results.append(fut.result())

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

One unparseable URL aborts the entire run.

parse_url raises ValueError on a line that doesn't match _URL_RE; that exception surfaces through fut.result() and terminates the whole loop, discarding every completed result. A single malformed line in the input file loses all work. Consider catching per-run failures and recording them as an error row instead.

🛡️ Example handling
-        for fut in as_completed(futures):
-            done += 1
-            if done % 50 == 0 or done == len(urls):
-                print(f"  {done}/{len(urls)} done", file=sys.stderr)
-            results.append(fut.result())
+        for fut in as_completed(futures):
+            done += 1
+            if done % 50 == 0 or done == len(urls):
+                print(f"  {done}/{len(urls)} done", file=sys.stderr)
+            line = futures[fut]
+            try:
+                results.append(fut.result())
+            except Exception as exc:  # noqa: BLE001
+                print(f"WARNING: skipping {line!r}: {exc}", file=sys.stderr)
🤖 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/kubelet-version-check/get_kubestat.py` around lines 263 -
267, Handle exceptions from each future independently in the loop consuming
`as_completed(futures)`: wrap `fut.result()` in a try/except, catch the parsing
failure such as `ValueError`, and append an error row matching the existing
result schema instead of aborting. Preserve progress reporting and continue
processing all remaining URLs.

Comment on lines +272 to +274
widths = [
max(len(hdr[i]), *(len(r[i]) for r in results)) for i in range(4)
]

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Crash on empty input: max() receives a single int.

When results is empty (empty/all-blank input file), *(len(r[i]) for r in results) expands to nothing, so this becomes max(len(hdr[i])) — a single int argument — which raises TypeError: 'int' object is not iterable. The tool aborts instead of printing an empty table.

🐛 Proposed fix
-    widths = [
-        max(len(hdr[i]), *(len(r[i]) for r in results)) for i in range(4)
-    ]
+    widths = [
+        max([len(hdr[i]), *(len(r[i]) for r in results)]) for i in range(4)
+    ]
📝 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
widths = [
max(len(hdr[i]), *(len(r[i]) for r in results)) for i in range(4)
]
widths = [
max([len(hdr[i]), *(len(r[i]) for r in results)]) for i in range(4)
]
🤖 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/kubelet-version-check/get_kubestat.py` around lines 272 -
274, Update the width calculation in get_kubestat.py so it handles an empty
results collection without calling max() with only a single integer. In the
table-formatting logic around widths, compute each column width from the header
plus any available row lengths, preserving header-only widths when results is
empty so the tool can print an empty table.

Comment on lines +49 to +53
KUBELET_CHECK="${CLAUDE_PLUGIN_ROOT}/skills/kubelet-version-check/get_kubestat.py"
if [ ! -f "$KUBELET_CHECK" ]; then
KUBELET_CHECK=$(find ~/.claude/plugins -type f -path "*/ci/skills/kubelet-version-check/get_kubestat.py" 2>/dev/null | sort | head -1)
fi
if [ -z "$KUBELET_CHECK" ] || [ ! -f "$KUBELET_CHECK" ]; then echo "ERROR: get_kubestat.py not found" >&2; exit 2; fi

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove the fallback that executes code from ~/.claude/plugins.

This searches an agent configuration directory and executes whichever matching file sorts first. A stale or attacker-controlled plugin can run with the user’s credentials. Use only CLAUDE_PLUGIN_ROOT or another explicitly trusted script path.

🧰 Tools
🪛 SkillSpector (2.3.7)

[error] 51: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files.

(Agent Snooping (AS1))

🤖 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/kubelet-version-check/SKILL.md` around lines 49 - 53,
Remove the ~/.claude/plugins fallback from the KUBELET_CHECK initialization. Use
only the script under CLAUDE_PLUGIN_ROOT (or another explicitly trusted fixed
path), then retain the existence check and error exit without searching or
executing files from the agent configuration directory.

Source: Linters/SAST tools

Comment on lines +96 to +104
## Error Handling

The script handles each run independently rather than aborting the whole batch; per-run problems are reported inline in that run's row:

- **Unparseable URL**: raises a `ValueError` for that line (fix or remove the offending URL).
- **Missing `finished.json`**: status is reported as `unknown`.
- **No e2e step directory**: kubelet version is `no e2e step`.
- **No nodes artifact**: kubelet version is `no nodes file`.
- **Version doesn't match the expected `v1.3x` pattern**: reported as `unexpected: <value>` so anomalies stand out.

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Align the documented error handling with the implementation.

The skill promises per-run inline errors, but parse_url() can raise ValueError and main() propagates fut.result(), so one malformed URL aborts the batch instead of producing an inline row. Catch and convert that exception in the script, or document batch termination accurately.

🧰 Tools
🪛 SkillSpector (2.3.7)

[error] 51: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files.

(Agent Snooping (AS1))

🤖 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/kubelet-version-check/SKILL.md` around lines 96 - 104, The
documented per-run error handling does not match the implementation because
ValueError from parse_url() propagates through main() via fut.result(). Update
the script so main() catches per-run parse_url() failures and emits an inline
error row while continuing other runs, then keep the Error Handling
documentation aligned with that behavior.

Comment on lines +46 to +48
PAYLOAD_BLOCKING=$(find ~/.claude/plugins -type f -path "*/ci/skills/payload-blocking-runs/payload_blocking_runs.py" 2>/dev/null | sort | head -1)
fi
if [ -z "$PAYLOAD_BLOCKING" ] || [ ! -f "$PAYLOAD_BLOCKING" ]; then echo "ERROR: payload_blocking_runs.py not found" >&2; exit 2; fi

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove the fallback that executes code from ~/.claude/plugins.

This searches an agent configuration directory and executes whichever matching file sorts first. A stale or attacker-controlled plugin can therefore run with the user’s credentials. Use only CLAUDE_PLUGIN_ROOT or an explicitly trusted script path, and fail closed otherwise.

🧰 Tools
🪛 SkillSpector (2.3.7)

[error] 46: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files.

(Agent Snooping (AS1))

🤖 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/payload-blocking-runs/SKILL.md` around lines 46 - 48,
Remove the ~/.claude/plugins fallback lookup from the PAYLOAD_BLOCKING
initialization. Resolve the script only through CLAUDE_PLUGIN_ROOT or another
explicitly trusted path, and retain the validation that fails closed when no
valid payload_blocking_runs.py is found.

Source: Linters/SAST tools

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

openshift-ci Bot commented Jul 18, 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.

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

Labels

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.

1 participant