Skip to content

Conversation

@Prucek
Copy link
Member

@Prucek Prucek commented Nov 26, 2025

Running this plugin from the claude cli will result in an automated pre-branching. And it should create a PR similar to this one: openshift/release#71837

@openshift-ci-robot
Copy link
Contributor

Pipeline controller notification
This repository is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@coderabbitai
Copy link

coderabbitai bot commented Nov 26, 2025

Walkthrough

Adds a new Claude plugin manifest and documentation for OpenShift pre-branching automation, plus nine executable Bash skills that implement branch creation, config copying/updating, gating/job updates, validation, Prow regeneration, analysis-job bumps, and commit creation.

Changes

Cohort / File(s) Summary
Plugin manifest & docs
.claude/.claude-plugin/plugin.json, .claude/commands/pre-branching.md
New Claude plugin manifest describing the pre-branching command, nine skills, permissions, and config keys; plus a detailed pre-branching workflow document.
Branch & workflow orchestration
.claude/.claude-plugin/skills/create-pre-branching-branch.sh, .claude/.claude-plugin/skills/create-pre-branching-commits.sh
Scripts to create/check out the pre-branching Git branch and to create up to three sequential pre-branching commits with staged changes and messages.
Release-controller config management
.claude/.claude-plugin/skills/copy-release-controller-configs.sh, .claude/.claude-plugin/skills/copy-handcrafted-release-gating-jobs.sh
Copy and update release-controller JSON configs and handcrafted release-gating periodic job YAMLs for a new version, bump embedded version strings, and run generation steps.
Gating/job generation & update
.claude/.claude-plugin/skills/update-release-gating-jobs.sh, .claude/.claude-plugin/skills/regenerate-prow-jobs.sh
Build/run the generated-release-gating-jobs tool with parameters and run make jobs to regenerate Prow jobs.
Validation & auxiliary utilities
.claude/.claude-plugin/skills/validate-release-controller-config.sh, .claude/.claude-plugin/skills/download-sippy-config.sh
Validate release-controller configs via repository script; download latest Sippy config from GitHub.
Analysis job bumping
.claude/.claude-plugin/skills/bump-analysis-jobs.sh
Script with embedded Python to find, bump, and append modified analysis job YAML blocks across multiarch and release periodic job files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Pay attention to:
    • bump-analysis-jobs.sh: embedded Python logic for extracting and modifying YAML job blocks and job-name matching.
    • copy-release-controller-configs.sh and copy-handcrafted-release-gating-jobs.sh: file globbing, copy/skip logic, and version string bumping order/regex to avoid double replacements.
    • create-pre-branching-commits.sh: staged-change detection and commit message correctness.
    • update-release-gating-jobs.sh: build invocation and passed flags/paths.
    • plugin.json: ensure skill names, script paths, and declared permissions align with added scripts.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 9fafc9e and c0f884c.

📒 Files selected for processing (11)
  • .claude/.claude-plugin/plugin.json (1 hunks)
  • .claude/.claude-plugin/skills/bump-analysis-jobs.sh (1 hunks)
  • .claude/.claude-plugin/skills/copy-handcrafted-release-gating-jobs.sh (1 hunks)
  • .claude/.claude-plugin/skills/copy-release-controller-configs.sh (1 hunks)
  • .claude/.claude-plugin/skills/create-pre-branching-branch.sh (1 hunks)
  • .claude/.claude-plugin/skills/create-pre-branching-commits.sh (1 hunks)
  • .claude/.claude-plugin/skills/download-sippy-config.sh (1 hunks)
  • .claude/.claude-plugin/skills/regenerate-prow-jobs.sh (1 hunks)
  • .claude/.claude-plugin/skills/update-release-gating-jobs.sh (1 hunks)
  • .claude/.claude-plugin/skills/validate-release-controller-config.sh (1 hunks)
  • .claude/commands/pre-branching.md (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .claude/commands/pre-branching.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • .claude/.claude-plugin/skills/copy-release-controller-configs.sh
  • .claude/.claude-plugin/skills/regenerate-prow-jobs.sh
  • .claude/.claude-plugin/skills/validate-release-controller-config.sh
  • .claude/.claude-plugin/skills/create-pre-branching-branch.sh
  • .claude/.claude-plugin/skills/update-release-gating-jobs.sh
  • .claude/.claude-plugin/skills/download-sippy-config.sh
🔇 Additional comments (4)
.claude/.claude-plugin/skills/copy-handcrafted-release-gating-jobs.sh (2)

75-90: Ensure version extraction and replacement handles edge cases safely.

Lines 75–90 extract and bump all versions in descending order to avoid double-replacement. However, there are edge cases to consider:

  1. Silent failure if no versions found: If grep matches nothing, the loop silently does nothing.
  2. Partial matches: The regex "${MAJOR_VERSION}\.[0-9]+" will match any occurrence; unrelated version strings in comments or other contexts could be unintentionally bumped.

Add validation and logging:

 # Find all unique version numbers in the file and sort them in descending order
 # This ensures we replace from highest to lowest to avoid double-replacement
-VERSIONS=$(grep -oE "${MAJOR_VERSION}\.[0-9]+" "$DST_FILE" | sort -t. -k2 -nr | uniq)
+VERSIONS=$(grep -oE "${MAJOR_VERSION}\.[0-9]+" "$DST_FILE" | sort -t. -k2 -nr | uniq)
+
+if [ -z "$VERSIONS" ]; then
+    echo "WARNING: No version strings found matching pattern ${MAJOR_VERSION}.* in $DST_FILE"
+fi
 
 # Bump each version found in the file
 for VERSION in $VERSIONS; do
     # Extract minor version
     MINOR="${VERSION##*.}"
     # Calculate new minor version
     NEW_MINOR=$((MINOR + 1))
     NEW_VER="${MAJOR_VERSION}.${NEW_MINOR}"
 
     # Escape dots for sed
     VERSION_ESCAPED="${VERSION//./\\.}"
 
     echo "  Bumping ${VERSION} → ${NEW_VER}"
-    sed -i "s/${VERSION_ESCAPED}/${NEW_VER}/g" "$DST_FILE"
+    if ! sed -i "s/${VERSION_ESCAPED}/${NEW_VER}/g" "$DST_FILE"; then
+        echo "ERROR: sed replacement failed for ${VERSION}" >&2
+        exit 1
+    fi
 done

95-106: Add error handling and validation for make commands.

Lines 98 and 104 execute make release-controllers and make jobs without validating success or handling errors. If these commands fail, the script exits due to set -euo pipefail, but there's no feedback about what failed or why.

Add explicit error handling:

 # Run make release-controllers
 echo "Running 'make release-controllers'..."
 cd "${RELEASE_REPO}"
-make release-controllers
+if ! make release-controllers; then
+    echo "ERROR: 'make release-controllers' failed" >&2
+    exit 1
+fi
 echo "✓ Release controllers generated"
 echo ""
 
 # Run make jobs
 echo "Running 'make jobs'..."
-make jobs
+if ! make jobs; then
+    echo "ERROR: 'make jobs' failed" >&2
+    exit 1
+fi
 echo "✓ Prow jobs regenerated"
 echo ""
.claude/.claude-plugin/skills/create-pre-branching-commits.sh (2)

26-29: Reconsider the logic for checking if there are any changes to commit.

Line 26 exits if there are no changes across the entire repository. However, the script then proceeds to stage and commit changes in three separate sections. It's possible that:

  1. Some sections have changes while others don't (which is fine per the conditional commits).
  2. The initial check is overly restrictive and could prevent valid partial commits.

Consider removing this check or refining it to only warn if NO commits are created at the end. This allows the script to attempt staging each section independently:

-# Check if there are changes to commit
-if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
-    echo "No changes to commit!"
-    exit 1
-fi
+# Note: We will check for staged changes in each section and commit only if changes exist

Then, after all three commit attempts, check if at least one commit was created:

# After the three commit sections, add:
if [ "$(git rev-list --count HEAD@{1}..HEAD)" -eq 0 ]; then
    echo "ERROR: No commits were created (no changes in any section)"
    exit 1
fi

41-44: Verify commit message accuracy and completeness.

Line 41–44 embeds a hardcoded command example in the commit message:

Generated by:
./generated-release-gating-jobs --current-release=${OLD_VERSION} --release-repo=${RELEASE_REPO} --sippy-config=/tmp/sippy-openshift.yaml --interval=168

This is useful for traceability, but it may not accurately reflect the actual command that was run or the parameters that were passed. Consider:

  1. If the command is predictable, this is fine as a documentation hint.
  2. If the command varies, parameterize it or add a comment that this is an example.

If this is always the exact command, keep it. If not, consider:

-git commit -m "bump release gating jobs from Sippy
-
-Generated by:
-./generated-release-gating-jobs --current-release=${OLD_VERSION} --release-repo=${RELEASE_REPO} --sippy-config=/tmp/sippy-openshift.yaml --interval=168"
+git commit -m "bump release gating jobs from Sippy
+
+Updated from version ${OLD_VERSION} to ${NEW_VERSION}.
+Generated by the pre-branching automation workflow."

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (7)
.claude/.claude-plugin/skills/download-sippy-config.sh (1)

1-31: Consider adding retry logic for network resilience.

The script uses curl without retry mechanism. Transient network failures during download could cause unexpected script termination. Since this is run as part of a multi-step workflow, consider adding exponential backoff retry logic to improve resilience.

# Consider adding a retry wrapper:
retry_curl() {
  local max_attempts=3
  local timeout=10
  local attempt=1
  local delay=1

  while [ $attempt -le $max_attempts ]; do
    if curl -sSL --max-time "$timeout" -o "$1" "$2"; then
      return 0
    fi
    if [ $attempt -lt $max_attempts ]; then
      echo "Download failed, retrying in ${delay}s..."
      sleep "$delay"
      delay=$((delay * 2))
    fi
    attempt=$((attempt + 1))
  done
  return 1
}

# Then use: retry_curl "${OUTPUT_PATH}" "https://raw.githubusercontent.com/..."
.claude/.claude-plugin/skills/validate-release-controller-config.sh (1)

36-36: Clarify the error message context.

Line 36 mentions "manual job copying may be required" as a warning when validation fails, but this message is ambiguous—it's unclear when manual intervention is needed versus when it should fail the entire operation. Either clarify the condition or consider whether this should fail faster to prevent partial execution.

Consider either:

  1. Remove the soft-failure messaging and let the validation script's exit code determine the flow.
  2. Document in comments when manual intervention is needed.
.claude/.claude-plugin/skills/create-pre-branching-branch.sh (1)

23-28: Add validation that release repository exists and is a Git repository.

The script assumes RELEASE_REPO is a valid Git repository but doesn't verify this before attempting operations. Early validation improves error messages for users.

cd "${RELEASE_REPO}"

# Add early validation after cd:
+if ! git rev-parse --git-dir > /dev/null 2>&1; then
+  echo "✗ Error: '${RELEASE_REPO}' is not a valid Git repository"
+  exit 1
+fi
.claude/commands/pre-branching.md (1)

158-164: Clarify template variable substitution mechanism.

The documentation uses template variables (e.g., {{OLD_VERSION}}) but doesn't explain how/when they get substituted. Are these:

  • Substituted by Claude before presenting to user?
  • Substituted by the plugin manifest system?
  • Expected to be manually replaced by the user?

This is crucial for users following this workflow. Add a sentence clarifying the substitution mechanism, ideally in a prominent "Setup" or "Before You Start" section.

Add a setup section at the beginning:

## Setup

Before running these commands, ensure the following template variables are set:
- `{{OLD_VERSION}}`: Previous release version (e.g., "4.21")
- `{{NEW_VERSION}}`: New release version (e.g., "4.22")  
- `{{RELEASE_REPO}}`: Absolute path to your release repository clone
- `{{CI_TOOLS_REPO}}`: Absolute path to your ci-tools repository clone

[Explain how these get substituted: by Claude, by shell, manually by user, etc.]
.claude/.claude-plugin/skills/create-pre-branching-commits.sh (2)

41-44: Hardcoded Sippy config path in commit message may become stale.

Line 44 hardcodes the Sippy config path /tmp/sippy-openshift.yaml in the commit message. This path was passed as an argument to the skill but is then hardcoded in the commit message. If users change the download location (e.g., via the optional parameter in download-sippy-config.sh), the commit message will be inaccurate.

Either:

  1. Accept SIPPY_CONFIG as a 4th argument to this script, or
  2. Use a command-substitution to extract the actual Sippy config path from recent git changes, or
  3. Document in comments that the hardcoded path assumes default behavior

For now, consider adding a 4th optional parameter:

+SIPPY_CONFIG="${4:-/tmp/sippy-openshift.yaml}"
 
 # Then in line 44:
-Generated by:
-./generated-release-gating-jobs --current-release=${OLD_VERSION} --release-repo=${RELEASE_REPO} --sippy-config=/tmp/sippy-openshift.yaml --interval=168"
+Generated by:
+./generated-release-gating-jobs --current-release=${OLD_VERSION} --release-repo=${RELEASE_REPO} --sippy-config=${SIPPY_CONFIG} --interval=168"

26-29: Simplify complex git change detection condition.

Line 26 combines three git conditions with && which is somewhat hard to parse. While functionally correct, consider extracting into a helper function for clarity, or use git status --porcelain which may be more readable:

-if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
+# Check if there are any uncommitted changes (staged, unstaged, or untracked)
+if git diff-index --quiet HEAD -- && [ -z "$(git ls-files --others --exclude-standard)" ]; then
     echo "No changes to commit!"
     exit 1
 fi

Or alternatively:

+HAS_CHANGES=$(git status --porcelain | grep -c '^' || true)
+if [ "$HAS_CHANGES" -eq 0 ]; then
+  echo "No changes to commit!"
+  exit 1
+fi
.claude/.claude-plugin/skills/bump-analysis-jobs.sh (1)

50-51: Document YAML parsing assumptions and add robustness checks.

The regex pattern (line 50) assumes strict YAML formatting with 2-space indentation and a specific structure (\n- .*?\n name: {job_name}\n). If YAML formatting varies, job extraction may silently fail or extract incomplete blocks.

Add comments explaining YAML structure assumptions, and consider adding validation of extracted YAML blocks:

 def extract_jobs(file_path, job_names):
     """Extract complete job definitions for given job names from a YAML file"""
+    # NOTE: This assumes YAML list items with 2-space indentation
+    # and 'name:' field at depth 1. Breaks if indentation differs.
     with open(file_path, 'r') as f:
         content = f.read()
 
     jobs = []
     for job_name in job_names:
-        # Find the job by name - match from "- " to the next "- " or end of file
+        # Find the job by name - match from "- " to the next "- " or end of file.
+        # Pattern assumes 2-space indentation (YAML standard but not validated).
         pattern = rf'(\n- .*?\n  name: {re.escape(job_name)}\n.*?)(?=\n- |\Z)'
         match = re.search(pattern, content, re.DOTALL)
         if match:
             jobs.append(match.group(1))
             print(f"  ✓ Found job: {job_name}", file=sys.stderr)
+            # Optionally validate extracted block is valid YAML list item
         else:
             print(f"  ✗ Could not find job: {job_name}", file=sys.stderr)

Alternatively, consider using a proper YAML parser (e.g., PyYAML) for more robust parsing.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 8d82283 and 9fafc9e.

📒 Files selected for processing (10)
  • .claude/.claude-plugin/plugin.json (1 hunks)
  • .claude/.claude-plugin/skills/bump-analysis-jobs.sh (1 hunks)
  • .claude/.claude-plugin/skills/copy-release-controller-configs.sh (1 hunks)
  • .claude/.claude-plugin/skills/create-pre-branching-branch.sh (1 hunks)
  • .claude/.claude-plugin/skills/create-pre-branching-commits.sh (1 hunks)
  • .claude/.claude-plugin/skills/download-sippy-config.sh (1 hunks)
  • .claude/.claude-plugin/skills/regenerate-prow-jobs.sh (1 hunks)
  • .claude/.claude-plugin/skills/update-release-gating-jobs.sh (1 hunks)
  • .claude/.claude-plugin/skills/validate-release-controller-config.sh (1 hunks)
  • .claude/commands/pre-branching.md (1 hunks)
🔇 Additional comments (2)
.claude/.claude-plugin/skills/regenerate-prow-jobs.sh (1)

1-30: Verify prerequisites (make, go) are available.

The script assumes make and underlying build tools are available but doesn't verify they exist. When invoked in unfamiliar environments, the error messages may be cryptic if prerequisites are missing.

Consider adding a prerequisite check:

# After set -euo pipefail, add:
if ! command -v make &> /dev/null; then
  echo "✗ Error: 'make' not found in PATH"
  exit 1
fi

Verify that the prerequisite chain for make jobs (e.g., Go, Python, etc.) is well-documented or checked.

.claude/.claude-plugin/plugin.json (1)

11-11: Verify all skill scripts and settings file are included in the PR.

The manifest references a settings file settings.pre-branching.json (line 11) and eight skill scripts (e.g., skills/create-pre-branching-branch.sh at line 22). Confirm these files are present in the PR.

Also applies to: 18-103

Comment on lines +105 to +119
"RELEASE_REPO": "/home/prucek/work/release",
"CI_TOOLS_REPO": "/home/prucek/work/ci-tools"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Replace hardcoded developer paths with environment variables or relative paths.

Lines 105-106 contain absolute paths specific to the developer's machine (/home/prucek/work/...). These will not work for other developers or CI environments and appear to be accidentally committed developer config.

Use environment variables or relative paths instead. For example:

- "config": {
-   "RELEASE_REPO": "/home/prucek/work/release",
-   "CI_TOOLS_REPO": "/home/prucek/work/ci-tools"
- }
+ "config": {
+   "RELEASE_REPO": "${RELEASE_REPO:-../release}",
+   "CI_TOOLS_REPO": "${CI_TOOLS_REPO:-../ci-tools}"
+ }

Alternatively, document in README how users should configure these paths before running the plugin, or derive them from the current working directory.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In .claude/.claude-plugin/plugin.json around lines 105-106 there are hardcoded
absolute developer paths (/home/prucek/work/release and
/home/prucek/work/ci-tools); replace these with configurable values by reading
environment variables (e.g., RELEASE_REPO and CI_TOOLS_REPO) or using paths
relative to the repository root (e.g., ./release, ./ci-tools) and provide
sensible fallbacks if unset; update the plugin to load those env vars or
construct relative paths at runtime and add instructions to the README (or a
README section) describing how to set the environment variables or where to
place the directories so CI and other developers don’t rely on machine-specific
paths.

Comment on lines +38 to +40
def bump_analysis_job(job_text, old_ver, new_ver):
"""Replace version numbers in a job definition"""
return job_text.replace(old_ver, new_ver)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace naive string replacement with semantic version bumping.

The bump_analysis_job function uses string.replace() to swap old and new versions (line 40), which will replace all occurrences of the old_version throughout the entire job block. This risks corrupting job definitions if the version string appears in unintended contexts (e.g., image URIs, configuration values, comments).

Use targeted regex replacement that only updates version strings in specific YAML fields. For example:

 def bump_analysis_job(job_text, old_ver, new_ver):
-    """Replace version numbers in a job definition"""
-    return job_text.replace(old_ver, new_ver)
+    """Replace version numbers in specific YAML fields"""
+    # Only replace version in predictable fields (name, image tag refs, etc.)
+    # Avoid replacing in unrelated config or comments
+    result = job_text
+    # Replace in job name field
+    result = re.sub(
+        rf'(\n  name:.*?{re.escape(old_ver)})',
+        lambda m: m.group(1).replace(old_ver, new_ver),
+        result
+    )
+    # Add similar targeted replacements for other fields as needed
+    return result

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +60 to +83
def append_bumped_jobs(file_path, job_names, old_ver, new_ver):
"""Extract jobs, bump versions, and append to the file"""
print(f"\nProcessing {file_path}...", file=sys.stderr)
jobs = extract_jobs(file_path, job_names)

if not jobs:
print(f" No jobs found in {file_path}", file=sys.stderr)
return False

# Read the current file
with open(file_path, 'r') as f:
content = f.read()

# Bump versions in the extracted jobs
bumped_jobs = [bump_analysis_job(job, old_ver, new_ver) for job in jobs]

# Append the bumped jobs to the file
new_content = content.rstrip() + '\n' + '\n'.join(bumped_jobs) + '\n'

with open(file_path, 'w') as f:
f.write(new_content)

print(f" ✓ Added {len(bumped_jobs)} bumped jobs", file=sys.stderr)
return True
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add idempotency guards to prevent duplicate job entries on repeated runs.

The append_bumped_jobs function unconditionally appends bumped jobs to the YAML file (line 77) without checking if they already exist. Running this script multiple times will create duplicate job entries, corrupting the configuration.

Modify the function to check if the bumped jobs already exist in the file before appending. For example:

 def append_bumped_jobs(file_path, job_names, old_ver, new_ver):
     """Extract jobs, bump versions, and append to the file"""
     print(f"\nProcessing {file_path}...", file=sys.stderr)
     jobs = extract_jobs(file_path, job_names)
 
     if not jobs:
         print(f"  No jobs found in {file_path}", file=sys.stderr)
         return False
 
     # Read the current file
     with open(file_path, 'r') as f:
         content = f.read()
 
+    # Bump versions in the extracted jobs
+    bumped_jobs = [bump_analysis_job(job, old_ver, new_ver) for job in jobs]
+
+    # Check if bumped jobs already exist in file to avoid duplicates
+    existing_jobs = '\n'.join(bumped_jobs) in content
+    if existing_jobs:
+        print(f"  ℹ Bumped jobs already exist in {file_path}, skipping append", file=sys.stderr)
+        return True
+
     # Bump versions in the extracted jobs
-    bumped_jobs = [bump_analysis_job(job, old_ver, new_ver) for job in jobs]
 
     # Append the bumped jobs to the file
     new_content = content.rstrip() + '\n' + '\n'.join(bumped_jobs) + '\n'

Alternatively, replace entire job entries instead of appending, or use a more robust YAML library that maintains referential integrity.

🤖 Prompt for AI Agents
In .claude/.claude-plugin/skills/bump-analysis-jobs.sh around lines 60–83, the
function append_bumped_jobs currently unconditionally appends bumped job YAML
fragments which causes duplicate job entries on repeated runs; update it to be
idempotent by detecting existing bumped jobs and either skipping or replacing
them before writing. Specifically, parse the current file (prefer using a YAML
loader) to identify jobs by their unique key/name/ID, for each bumped job check
if an entry with the same key already exists and if so replace that entry (or
skip appending if identical), otherwise append the new job; only write the
merged/updated YAML back to disk so repeated runs do not create duplicates.

Comment on lines +86 to +97
multiarch_file = 'ci-operator/jobs/openshift/multiarch/openshift-multiarch-master-periodics.yaml'
multiarch_jobs = [
f'periodic-ci-openshift-multiarch-master-nightly-{old_version}-install-analysis-all-multi-p-p',
f'periodic-ci-openshift-multiarch-master-nightly-{old_version}-install-analysis-all-ppc64le',
f'periodic-ci-openshift-multiarch-master-nightly-{old_version}-install-analysis-all-s390x',
]

print("\n=== Multiarch Analysis Jobs ===", file=sys.stderr)
append_bumped_jobs(multiarch_file, multiarch_jobs, old_version, new_version)

# Bump release jobs
release_file = 'ci-operator/jobs/openshift/release/openshift-release-master-periodics.yaml'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add file existence checks before read/write operations.

The script assumes both YAML files exist at lines 86 and 97 but does not validate their presence. If a file is missing, the script will fail with a cryptic Python traceback instead of a clear error message.

Add existence checks before processing:

+ # Verify files exist
+ multiarch_file = 'ci-operator/jobs/openshift/multiarch/openshift-multiarch-master-periodics.yaml'
+ release_file = 'ci-operator/jobs/openshift/release/openshift-release-master-periodics.yaml'
+ 
+ if [ ! -f "$multiarch_file" ]; then
+     echo "Error: Multiarch jobs file not found: $multiarch_file" >&2
+     exit 1
+ fi
+ if [ ! -f "$release_file" ]; then
+     echo "Error: Release jobs file not found: $release_file" >&2
+     exit 1
+ fi

Or add validation in Python before opening files.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In .claude/.claude-plugin/skills/bump-analysis-jobs.sh around lines 86 to 97 the
script calls append_bumped_jobs on YAML paths without checking the files exist;
add explicit existence checks for multiarch_file and release_file before any
read/write operations and fail fast with a clear error message if either is
missing. Implement this by testing for file presence (e.g., os.path.exists or
shell test -f depending on runtime) prior to calling append_bumped_jobs, and
return or exit with a descriptive error that includes the missing path so
subsequent file reads/writes won’t produce a cryptic traceback.

]

print("\n=== Multiarch Analysis Jobs ===", file=sys.stderr)
append_bumped_jobs(multiarch_file, multiarch_jobs, old_version, new_version)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Check return values from append_bumped_jobs to detect failures.

Lines 94 and 105 call append_bumped_jobs but ignore its return value. If jobs are not found in a file, the function returns False, but the script continues and still exits with status 0, masking the failure.

Capture and handle the return value:

 print("\n=== Multiarch Analysis Jobs ===", file=sys.stderr)
-append_bumped_jobs(multiarch_file, multiarch_jobs, old_version, new_version)
+if not append_bumped_jobs(multiarch_file, multiarch_jobs, old_version, new_version):
+    print("Warning: No multiarch jobs were processed", file=sys.stderr)
 
 # Bump release jobs
 release_file = 'ci-operator/jobs/openshift/release/openshift-release-master-periodics.yaml'
@@ -102,7 +102,9 @@
 ]
 
 print("\n=== Release Analysis Jobs ===", file=sys.stderr)
-append_bumped_jobs(release_file, release_jobs, old_version, new_version)
+if not append_bumped_jobs(release_file, release_jobs, old_version, new_version):
+    print("Error: No release jobs were processed", file=sys.stderr)
+    sys.exit(1)

Consider whether missing jobs should be warnings (non-fatal) or errors (fatal) based on your workflow expectations.

Also applies to: 105-105

🤖 Prompt for AI Agents
.claude/.claude-plugin/skills/bump-analysis-jobs.sh around lines 94 and 105: the
script calls append_bumped_jobs(...) but ignores its boolean return value, so
failures (returns False when jobs not found) are masked; update both call sites
to capture the return value, check it, and handle failures according to policy —
either emit a warning and continue or log an error and exit with a non-zero
status (prefer exiting if missing jobs should be fatal); ensure the log message
includes which file and versions failed and return/exit appropriately so the
script surface failures instead of exiting 0.

Comment on lines +14 to +42
OLD_VERSION="$1"
NEW_VERSION="$2"
RELEASE_REPO="$3"
RELEASES_DIR="${RELEASE_REPO}/core-services/release-controller/_releases"

echo "Bumping release controller configs: ${OLD_VERSION} → ${NEW_VERSION}"

# Escape dots for sed patterns
OLD_ESCAPED="${OLD_VERSION//./\\.}"

# Calculate previous version for nested references
PREV_MINOR=$((${OLD_VERSION##*.} - 1))
PREV_VERSION="${OLD_VERSION%.*}.${PREV_MINOR}"
PREV_ESCAPED="${PREV_VERSION//./\\.}"
CURR_MINOR=$((${NEW_VERSION##*.} - 1))
CURR_VERSION="${NEW_VERSION%.*}.${CURR_MINOR}"

# Find all files with old_version in their name and bump them
find "$RELEASES_DIR" -type f -name "*${OLD_VERSION}*.json" 2>/dev/null | while read -r src_file; do
# Skip if already processed/destination exists
dst_file="${src_file//${OLD_VERSION}/${NEW_VERSION}}"
[ -f "$dst_file" ] && continue

echo " $(basename "$src_file") → $(basename "$dst_file")"
cp "$src_file" "$dst_file"

# Bump version strings inside the file
sed -i "s/${OLD_ESCAPED}/${NEW_VERSION}/g" "$dst_file"
sed -i "s/${PREV_ESCAPED}/${CURR_VERSION}/g" "$dst_file"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Address fragile version parsing and sed backup risk.

Multiple issues in this section:

  1. Version parsing assumes X.Y format (Lines 25-28): The arithmetic PREV_MINOR=$((${OLD_VERSION##*.} - 1)) will fail or produce unexpected results for versions like "4" (no dot), "4.21.1" (too many dots), or when subtracting results in negative numbers. For example, version "4.0" would produce PREV_MINOR = -1.

  2. sed -i without backup (Line 41-42): In-place sed edits without backup (with -i .bak or similar) risk data corruption if sed fails. The script uses set -euo pipefail but sed errors within the file loop won't necessarily stop execution.

  3. Filename pattern matching could be too broad (Line 32): Files matching *${OLD_VERSION}*.json could unintentionally match unrelated files. For example, version "4.2" would match "4.21" files.

Consider this improved version:

-# Calculate previous version for nested references
-PREV_MINOR=$((${OLD_VERSION##*.} - 1))
-PREV_VERSION="${OLD_VERSION%.*}.${PREV_MINOR}"
+# Validate version format and calculate previous version
+if [[ ! "$OLD_VERSION" =~ ^[0-9]+\.[0-9]+$ ]]; then
+  echo "✗ Error: OLD_VERSION must be in X.Y format (e.g., 4.21)"
+  exit 1
+fi
+
+MAJOR="${OLD_VERSION%.*}"
+MINOR="${OLD_VERSION##*.}"
+PREV_MINOR=$((MINOR - 1))
+if [ "$PREV_MINOR" -lt 0 ]; then
+  echo "✗ Error: Cannot calculate previous version for ${OLD_VERSION}"
+  exit 1
+fi
+PREV_VERSION="${MAJOR}.${PREV_MINOR}"

-find "$RELEASES_DIR" -type f -name "*${OLD_VERSION}*.json" 2>/dev/null | while read -r src_file; do
+# Use word-boundary pattern to avoid partial matches
+find "$RELEASES_DIR" -type f -name "*-${OLD_VERSION}.json" 2>/dev/null | while read -r src_file; do
     dst_file="${src_file//${OLD_VERSION}/${NEW_VERSION}}"
     [ -f "$dst_file" ] && continue
 
     echo "  $(basename "$src_file")$(basename "$dst_file")"
     cp "$src_file" "$dst_file"
 
     # Bump version strings inside the file
-    sed -i "s/${OLD_ESCAPED}/${NEW_VERSION}/g" "$dst_file"
-    sed -i "s/${PREV_ESCAPED}/${CURR_VERSION}/g" "$dst_file"
+    sed -i.bak "s/${OLD_ESCAPED}/${NEW_VERSION}/g" "$dst_file" && rm -f "${dst_file}.bak"
+    sed -i.bak "s/${PREV_ESCAPED}/${CURR_VERSION}/g" "$dst_file" && rm -f "${dst_file}.bak"
done

Also validate that RELEASES_DIR exists at line 17.

Comment on lines +24 to +42
cd "${CI_TOOLS_REPO}"

# Build the tool if not already built
echo "Building generated-release-gating-jobs..."
go build ./cmd/branchingconfigmanagers/generated-release-gating-jobs

# Run the bumper using Sippy config
echo ""
echo "Running generated-release-gating-jobs bumper..."
./generated-release-gating-jobs \
--current-release="${OLD_VERSION}" \
--release-repo="${RELEASE_REPO}" \
--sippy-config="${SIPPY_CONFIG}" \
--interval=168

echo ""
echo "=========================================="
echo "✓ Release gating jobs updated!"
echo "=========================================="
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing Go availability check and hardcoded interval should be parameterizable.

Two issues in this section:

  1. Go dependency (Line 28): The go build will fail cryptically if Go is not installed. Add a prerequisite check.
  2. Hardcoded interval (Line 37): The --interval=168 is hardcoded (presumably representing 168 hours/1 week). This should either be a parameter to the script or documented as to why it's always fixed at this value.
  3. Relative path (Line 33): The binary path ./generated-release-gating-jobs is relative to the current directory. Consider using an absolute path or storing the path in a variable for clarity.

Apply this diff:

# Add Go check after set -euo pipefail:
-set -euo pipefail
+set -euo pipefail
+
+if ! command -v go &> /dev/null; then
+  echo "✗ Error: 'go' not found in PATH"
+  exit 1
+fi

# Store binary path:
 cd "${CI_TOOLS_REPO}"
 
 # Build the tool if not already built
 echo "Building generated-release-gating-jobs..."
-go build ./cmd/branchingconfigmanagers/generated-release-gating-jobs
+go build -o generated-release-gating-jobs-bin ./cmd/branchingconfigmanagers/generated-release-gating-jobs
 
 # Run the bumper using Sippy config
 echo ""
 echo "Running generated-release-gating-jobs bumper..."
-./generated-release-gating-jobs \
+INTERVAL="${INTERVAL:-168}"  # Allow override, default 168 hours
+./generated-release-gating-jobs-bin \
   --current-release="${OLD_VERSION}" \
   --release-repo="${RELEASE_REPO}" \
   --sippy-config="${SIPPY_CONFIG}" \
-  --interval=168
+  --interval="${INTERVAL}"

Alternatively, if --interval=168 is intentionally fixed, document why in a comment.

🤖 Prompt for AI Agents
In .claude/.claude-plugin/skills/update-release-gating-jobs.sh around lines
24-42, add a runtime check for Go immediately after the existing set -euo
pipefail to fail fast with a clear message if go is missing; change the go build
invocation to write a named output binary (store that path in a variable, e.g.,
BIN_PATH) instead of relying on a relative executable name; introduce an
INTERVAL variable with a default (e.g., INTERVAL="${INTERVAL:-168}") and pass
that variable to the --interval flag so callers can override it; and use the
BIN_PATH variable when invoking the generated-release-gating-jobs binary (or add
a short comment if the 168-hour fixed interval must remain).

@openshift-ci openshift-ci bot requested review from droslean and pruan-rht November 26, 2025 14:43
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Nov 26, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: Prucek

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

The pull request process is described here

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 Nov 26, 2025
@Prucek
Copy link
Member Author

Prucek commented Nov 27, 2025

/override ci/prow/e2e
/test images
/override ci/prow/breaking-changes

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Nov 27, 2025

@Prucek: /override requires failed status contexts, check run or a prowjob name to operate on.
The following unknown contexts/checkruns were given:

  • e2e

Only the following failed contexts/checkruns were expected:

  • CodeRabbit
  • ci/prow/breaking-changes
  • ci/prow/checkconfig
  • ci/prow/codegen
  • ci/prow/e2e
  • ci/prow/format
  • ci/prow/frontend-checks
  • ci/prow/images
  • ci/prow/integration
  • ci/prow/integration-optional-test
  • ci/prow/lint
  • ci/prow/security
  • ci/prow/unit
  • ci/prow/validate-vendor
  • pull-ci-openshift-ci-tools-main-breaking-changes
  • pull-ci-openshift-ci-tools-main-checkconfig
  • pull-ci-openshift-ci-tools-main-codegen
  • pull-ci-openshift-ci-tools-main-e2e
  • pull-ci-openshift-ci-tools-main-format
  • pull-ci-openshift-ci-tools-main-frontend-checks
  • pull-ci-openshift-ci-tools-main-images
  • pull-ci-openshift-ci-tools-main-integration
  • pull-ci-openshift-ci-tools-main-integration-optional-test
  • pull-ci-openshift-ci-tools-main-lint
  • pull-ci-openshift-ci-tools-main-security
  • pull-ci-openshift-ci-tools-main-unit
  • pull-ci-openshift-ci-tools-main-validate-vendor
  • tide

If you are trying to override a checkrun that has a space in it, you must put a double quote on the context.

In response to this:

/override e2e
/test images
/override ci/prow/breaking-changes

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.

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Nov 27, 2025

@Prucek: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/breaking-changes c0f884c link false /test breaking-changes

Full PR test history. Your PR dashboard.

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. I understand the commands that are listed here.

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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants