openshift/create-cpo-override
Interactively create CPO image overrides — resolves images, verifies fixes, edits overrides.yaml, and prepares a PR
npx skills add https://github.com/openshift/hypershift --skill create-cpo-override
/create-cpo-override
Interactively creates control-plane-operator (CPO) image override entries in
hypershift-operator/controlplaneoperator-overrides/assets/overrides.yaml.
Automates image discovery, fix verification, YAML editing, and PR preparation
so the result is compatible with /validate-pr-override-images.
skopeo installedoc installed (for oc adm release info)gh CLI authenticatedgit fetch --all)Follow the steps below in order. Each step builds on the previous one.
Use AskUserQuestion for multi-choice prompts where indicated. For free-form
inputs, ask the user directly in conversation.
Many OCP payload images require authentication. Before any image operations,
probe for a working pull secret:
skopeo inspect against a known release payload image(e.g. quay.io/openshift-release-dev/ocp-release:4.18.0-multi). If it
succeeds, the container runtime's default auth is sufficient — no extra
flags needed.
$PULL_SECRET is set and points to an existingfile. If so, retry with --authfile "$PULL_SECRET" (and use
-a "$PULL_SECRET" for oc adm release info). Use this authfile for all
subsequent image operations.
If they don't have one, warn that payload-sourced images (Sources 1 & 2)
will be unavailable and only Konflux images (Source 3, public on
quay.io/redhat-user-workloads/) can be resolved automatically.
Collect the following from the user. Ask all questions up front (or in logical
groups) rather than one at a time.
Ask the user which platform(s) to override.
Options: aws, azure, or both.
Ask for the parent Jira ticket that motivates the override (e.g. OCPBUGS-86238).
This is used in comment markers and the commit message.
Ask which OCP minor branches need overrides (e.g. 4.20, 4.21).
For each branch, ask the user which z-stream versions to override. Accepted
formats:
| Input | Meaning |
|-------|---------|
| 4.20.0-4.20.24 | Explicit range |
| all | Every z-stream from X.Y.0 through the highest z-stream in the Cincinnati fast-X.Y channel |
| 4.20.15 | Single version |
When the user says all, resolve the range as follows:
curl -sH 'Accept: application/json' \
"https://api.openshift.com/api/upgrades_info/v1/graph?channel=fast-${BRANCH}&arch=multi" \
| python3 -c "
import json, sys
data = json.load(sys.stdin)
versions = [n['version'] for n in data.get('nodes', [])
if n['version'].startswith('${BRANCH}.')]
zs = sorted(int(v.split('.')[2]) for v in versions)
print(max(zs))
"
Then generate every z-stream from X.Y.0 through X.Y.<max_z> inclusive
(Cincinnati skips some z-streams like 4.20.7, 4.20.9 — clusters can still run
them, so include them all).
This is critical to avoid upgrade regressions. If a customer is on an
overridden version (e.g. 4.22.3) and upgrades to the next z-stream (e.g.
4.22.4), the override no longer applies. If that next z-stream does not
contain the fix in its payload, the customer regresses.
To determine whether the next z-stream after the override range needs to be
included, check the development cutoff date for that version against the
PR merge dates.
If the productpages MCP server is available, use it:
search_entities(q="OpenShift X.Y.z", kind="release")
browse_schedule(entity_id=<id>, q="X.Y.<max_z+1>")
X.Y.<max_z+1> Development Cut Off and note itsdate_finish.
gh pr view --json mergedAt).If all PRs merged before the cutoff date, the next z-stream will
include the fixes — no override needed for it. Add a comment in
overrides.yaml noting this (e.g. "4.22.4 does not need an override: both
PRs merged before the development cutoff").
If any PR merged after the cutoff date, extend the override range to
include that z-stream and repeat the check for the one after it.
If the productpages MCP server is NOT available, warn the user and ask
for the cutoff date:
> ⚠️ Cannot verify development cutoff dates — the Product Pages MCP server
> is not connected. Please check the development cutoff date for
> X.Y.<max_z+1> at https://pp.engineering.redhat.com and provide it here so
> I can verify the override range is complete.
When the user provides the cutoff date, compare it against the PR merge dates
(same logic as above). If any PR merged after the cutoff, extend the override
range and ask for the next z-stream's cutoff date. Repeat until the range is
safe.
Ask which GitHub PRs must be present in the override image. Accept PR URLs
(e.g. https://github.com/openshift/hypershift/pull/8593) or plain numbers
(e.g. 8593). Multiple PRs can be comma-separated.
PRs can be specified per-branch or globally (applied to all branches). If a PR
is a cherry-pick, the user should provide the branch-specific PR number.
Ask whether the user wants to provide specific images or have them
auto-resolved. If auto-resolved, proceed to Step 2.
For each branch that needs an image, try sources in this order. Stop at
the first source whose image contains all required PRs for that branch.
# Get the latest version in stable-X.Y
LATEST=$(curl -sH 'Accept: application/json' \
"https://api.openshift.com/api/upgrades_info/v1/graph?channel=stable-${BRANCH}&arch=multi" \
| python3 -c "
import json, sys
data = json.load(sys.stdin)
versions = [n['version'] for n in data.get('nodes', [])
if n['version'].startswith('${BRANCH}.')]
versions.sort(key=lambda v: [int(x) for x in v.split('.')])
print(versions[-1])
")
# Get the hypershift component's source commit from the payload using JSON output.
# This avoids needing skopeo access to the internal ocp-v4.0-art-dev repo.
COMMIT=$(oc adm release info \
${AUTHFILE:+-a "$AUTHFILE"} \
"quay.io/openshift-release-dev/ocp-release:${LATEST}-multi" \
-o json 2>/dev/null \
| jq -r '.references.spec.tags[] | select(.name == "hypershift") | .annotations["io.openshift.build.commit.id"]')
Verify all required PRs are ancestors of $COMMIT using
git merge-base --is-ancestor. If they are, the fix is already in the latest
stable payload — no override is needed for this branch. Report this to
the user and skip the branch.
Note: do not use the payload's internal image pullspec (ocp-v4.0-art-dev)
as the override image itself — that repo requires special registry access that
clusters may not have. If an override is still needed for older z-streams,
proceed to Source 3 (Konflux).
Same as Source 1 but query fast-${BRANCH} instead of stable-${BRANCH}.
If the fix is already in fast, report it and skip the branch.
REPO="quay.io/redhat-user-workloads/crt-redhat-acm-tenant/control-plane-operator-${BRANCH_HYPHEN}"
# BRANCH_HYPHEN is e.g. "4-21" (dots replaced with hyphens)
# List tags, filter for 40-char hex commit SHA tags (push builds)
TAGS=$(skopeo list-tags "docker://${REPO}" 2>/dev/null \
| python3 -c "
import json, sys, re
tags = json.load(sys.stdin).get('Tags', [])
# Push build tags are 40-char hex, no suffix
commits = [t for t in tags if re.fullmatch(r'[0-9a-f]{40}', t)]
for c in commits:
print(c)
")
For each candidate commit tag (check newest first — use git log to order
them), verify the required PRs are ancestors:
for PR_NUM in $REQUIRED_PRS; do
PR_MERGE=$(gh pr view "$PR_NUM" --repo openshift/hypershift \
--json mergeCommit --jq '.mergeCommit.oid // empty')
git merge-base --is-ancestor "$PR_MERGE" "$COMMIT_TAG"
done
If all PRs pass, get the digest-pinned reference:
DIGEST=$(skopeo inspect --override-os linux --override-arch amd64 \
"docker://${REPO}:${COMMIT_TAG}" 2>/dev/null \
| python3 -c "import json,sys; print(json.load(sys.stdin)['Digest'])")
IMAGE="${REPO}@${DIGEST}"
Source label: Konflux push build (commit ${COMMIT_TAG:0:12}).
If none of the above sources have an image containing all required PRs:
build-cpo-image dev skill for building a custom image locallycontrib/konflux/README.mdFor each (branch, image, PR) tuple, run the existing verification script:
.claude/skills/validate-pr-override-images/verify-pr-in-image.sh \
"$IMAGE" "$PR_NUM" "$(git rev-parse --show-toplevel)"
This script:
skopeo inspect to get the image's vcs-ref label (git commit)gh pr view to get the PR's merge commitgit merge-base --is-ancestor <pr-merge-commit> <image-commit>If any verification fails, report the failure and ask the user how to proceed
(provide a different image, skip that branch, or abort).
For each resolved image, confirm it is pullable (use the authfile from Step 0
if one was detected):
skopeo inspect --override-os linux --override-arch amd64 \
${AUTHFILE:+--authfile "$AUTHFILE"} \
"docker://${IMAGE}" > /dev/null 2>&1
Report any failures.
Display a summary table and ask the user to confirm before making changes:
=== CPO Override Summary ===
Ticket: OCPBUGS-XXXXX
Platform(s): azure
Branch 4.20:
Versions: 4.20.0 - 4.20.27 (28 entries)
Image: quay.io/redhat-user-workloads/crt-redhat-acm-tenant/control-plane-operator-4-20@sha256:155e4e...
Source: Konflux push build (commit 46f3b353fd5c)
PRs verified: #8593 PASS, #8565 PASS
Branch 4.21:
Versions: 4.21.0 - 4.21.18 (19 entries)
Image: quay.io/redhat-user-workloads/crt-redhat-acm-tenant/control-plane-operator-4-21@sha256:1b3f1b...
Source: Konflux push build (commit c84f8073)
PRs verified: #8565 PASS
Branch 4.22:
No override needed — fix landed before GA (PR #8564, commit d6c72d15)
Proceed with editing overrides.yaml? [Yes / No]
overrides.yamlModify hypershift-operator/controlplaneoperator-overrides/assets/overrides.yaml.
Placement rules:
aws or azure)testing: sectionFormat: Read the existing overrides.yaml and replicate its conventions
exactly — comment markers, indentation, and entry structure. Do not
hard-code a format here; the file itself is the source of truth.
go test ./hypershift-operator/controlplaneoperator-overrides/...
If tests fail, fix the YAML and re-run. Common issues:
Stage only the override file (and any new Konflux PDS files if needed):
git add hypershift-operator/controlplaneoperator-overrides/assets/overrides.yaml
# If Konflux PDS files were created:
# git add contrib/konflux/cpo_X_Y_stream.yaml
Commit message format:
<TICKET>: add CPO overrides for <short description>
Add <platform> CPO image overrides for <branches> to fix <description>.
<Branch that doesn't need override> does not need an override: the fix
(PR #NNNN, commit <short-sha>) landed before <version> and will be
included in the GA release.
- X.Y.0-X.Y.Z: <TICKET-per-branch> (PR #NNNN cherry-pick to release-X.Y)
- ...
Co-Authored-By: Claude Opus 4.6 <[email protected]>
The PR description must include validation contract lines that
/validate-pr-override-images parses. The contract format is documented in
docs/content/contribute/cpo-overrides.md
under "Validating Override Images Contain Claimed PRs". Generate those lines
from the branch-to-PRs mapping collected in Step 1.
Include a Summary section, the contract lines (outside code blocks so the
validator can parse them), and a Test plan section.
After creating the PR, run /validate-pr-override-images <pr-number> to
confirm the PR passes validation end-to-end. If it fails, fix the PR
description or image references and re-run until it passes.
Take openshift/create-cpo-override from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.