Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8c40151
claude: detect StatusContext check failures before merging
littlejawa Jul 27, 2026
552c578
claude: add rpm lockfile refresh PRs to Mintmaker filter
littlejawa Jul 29, 2026
c8e939e
claude: block merge when last on-push pipeline failed
littlejawa Jul 31, 2026
6cf47d7
claude: fix enterprise-contract FAILURE incorrectly treated as non-bl…
littlejawa Aug 18, 2026
5f2d517
claude: guard against set -u crash when option argument is missing
littlejawa Aug 21, 2026
f89b4a0
claude: require --repo and --branch together in check-merge-safety.sh
littlejawa Aug 21, 2026
66ddfab
claude: add submodule update PRs to Mintmaker filter
littlejawa Aug 21, 2026
5171662
claude: adding a watcher script for troubleshooting
littlejawa Aug 24, 2026
d7bb877
claude: simplify skill parameters and add no-arg auto-flow
littlejawa Aug 25, 2026
09bd206
claude: move Mintmaker/Nudge workflow logic into orchestration scripts
littlejawa Aug 25, 2026
a6ca028
claude: add test-fbc processing step after Mintmaker/Nudge queues drain
littlejawa Aug 26, 2026
e80b342
process-konflux-prs: match component names exactly in nudge processing
littlejawa Aug 27, 2026
070f7d1
process-konflux-prs: show other_checks_failed count in status column
littlejawa Aug 27, 2026
96f01e7
process-konflux-prs: add orchestrate script for Konflux PR processing…
littlejawa Aug 31, 2026
640f543
process-konflux-prs: add --dry-run support to all Konflux PR processi…
littlejawa Aug 31, 2026
1f1b1b1
process-konflux-prs: properly check label-pr.sh success in caller scr…
littlejawa Aug 31, 2026
3202741
process-konflux-prs: skip PRs with do-not-merge/hold label
littlejawa Sep 3, 2026
7bb405c
process-konflux-prs: always run nudge alongside mintmaker
littlejawa Sep 3, 2026
bec456c
process-konflux-prs: remove the skill definitiona and add a comprehen…
littlejawa Aug 31, 2026
8e8431a
process-test-fbc: skip ok-to-test label if build or checks failed
littlejawa Sep 11, 2026
14abce4
process-konflux-prs: Send notifications on slack
littlejawa Sep 21, 2026
57ebb44
process-konflux-prs: use --match-head-commit to prevent TOCTOU merge …
littlejawa Sep 22, 2026
88ba4ec
process-nudge: gate multi-PR ok-to-test labeling on merge safety
littlejawa Sep 22, 2026
863a611
process-konflux-prs: use actual blocking reason instead of hardcoded …
littlejawa Sep 24, 2026
62853f2
process-konflux-prs: preserve merge-pr.sh error output on failure
littlejawa Sep 25, 2026
99eb8e2
process-konflux-prs: add the pqc-enable image to the list of updates
littlejawa Oct 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
599 changes: 0 additions & 599 deletions .claude/commands/process-konflux-prs.md

This file was deleted.

366 changes: 168 additions & 198 deletions scripts/process-konflux-prs/README.md

Large diffs are not rendered by default.

100 changes: 93 additions & 7 deletions scripts/process-konflux-prs/check-merge-safety.sh
Original file line number Diff line number Diff line change
@@ -1,32 +1,54 @@
#!/bin/bash
# Check whether it is safe to merge a PR by verifying that no on-push pipeline
# is currently running for any of the components it would rebuild.
# Check whether it is safe to merge a PR by verifying:
# 1. No on-push pipeline is currently RUNNING for any of its components.
# 2. The last completed on-push pipeline for each component SUCCEEDED.
# (Checked via GitHub check-runs on recent commits of the target branch.)
Comment thread
littlejawa marked this conversation as resolved.
#
# Usage: ./check-merge-safety.sh --components "comp1 comp2 ..." [--namespace NAMESPACE]
# Usage: ./check-merge-safety.sh --components "comp1 comp2 ..."
# [--repo GITHUB_REPO] [--branch BRANCH]
# [--namespace NAMESPACE]
#
# --repo and --branch enable check 2 via the GitHub API (e.g. --repo
# openshift/sandboxed-containers-operator --branch devel). When omitted,
# only check 1 (running pipeline detection) is performed.
#
# Output: JSON {safe_to_merge: bool, blocking: [{component, pipeline, reason}]}
# safe_to_merge: true when no on-push pipeline is running for any component
# blocking: list of components with a running pipeline (empty when safe)
# safe_to_merge: true only when all components pass both checks
# blocking: list of blocking conditions (empty when safe)

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

COMPONENTS=()
NAMESPACE_ARG=""
GH_REPO=""
GH_BRANCH=""

while [[ $# -gt 0 ]]; do
case $1 in
--components)
[[ $# -ge 2 ]] || { echo "Usage: $0 --components 'comp1 comp2 ...' [--repo GITHUB_REPO] [--branch BRANCH] [--namespace NAMESPACE]" >&2; exit 1; }
IFS=' ' read -ra COMPONENTS <<< "$2"
shift 2
;;
--namespace)
[[ $# -ge 2 ]] || { echo "Usage: $0 --components 'comp1 comp2 ...' [--repo GITHUB_REPO] [--branch BRANCH] [--namespace NAMESPACE]" >&2; exit 1; }
NAMESPACE_ARG="--namespace $2"
shift 2
;;
--repo)
[[ $# -ge 2 ]] || { echo "Usage: $0 --components 'comp1 comp2 ...' [--repo GITHUB_REPO] [--branch BRANCH] [--namespace NAMESPACE]" >&2; exit 1; }
GH_REPO="$2"
shift 2
;;
--branch)
[[ $# -ge 2 ]] || { echo "Usage: $0 --components 'comp1 comp2 ...' [--repo GITHUB_REPO] [--branch BRANCH] [--namespace NAMESPACE]" >&2; exit 1; }
GH_BRANCH="$2"
shift 2
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
;;
*)
echo "Usage: $0 --components 'comp1 comp2 ...' [--namespace NAMESPACE]" >&2
echo "Usage: $0 --components 'comp1 comp2 ...' [--repo GITHUB_REPO] [--branch BRANCH] [--namespace NAMESPACE]" >&2
exit 1
;;
esac
Expand All @@ -37,7 +59,45 @@ if [[ ${#COMPONENTS[@]} -eq 0 ]]; then
exit 1
fi

if [[ ( -n "$GH_REPO" && -z "$GH_BRANCH" ) || ( -z "$GH_REPO" && -n "$GH_BRANCH" ) ]]; then
echo "Error: --repo and --branch must be supplied together" >&2
exit 1
fi

# Query GitHub for the most recent on-push check-run conclusion for a component.
# Walks back through the last 20 commits on the branch, stopping at the first
# commit that has the component's on-push check-run.
# Returns: "success", "failure", etc. from the check-run conclusion,
# "unknown" when no matching check-run exists in the last 20 commits,
# or "error" when the GitHub API query itself fails.
gh_last_push_conclusion() {
local gh_repo="$1"
local branch="$2"
local component="$3"
local check_name="Red Hat Konflux / ${component}-on-push"

local shas
shas=$(gh api "repos/${gh_repo}/commits?sha=${branch}&per_page=20" \
--jq '.[].sha' 2>/dev/null) || { echo "error"; return; }

while IFS= read -r sha; do
local raw conclusion
raw=$(gh api "repos/${gh_repo}/commits/${sha}/check-runs" \
2>/dev/null) || { echo "error"; return; }
Comment thread
coderabbitai[bot] marked this conversation as resolved.
conclusion=$(echo "$raw" | jq -r --arg n "$check_name" \
'([.check_runs[] | select(.name == $n)] | .[0].conclusion) // ""') \
|| { echo "error"; return; }
if [[ -n "$conclusion" ]]; then
echo "$conclusion"
return
fi
done <<< "$shas"

echo "unknown"
}

blocking='[]'

for component in "${COMPONENTS[@]}"; do
if ! result=$("$SCRIPT_DIR/check-pipeline-status.sh" --component "$component" $NAMESPACE_ARG 2>&1); then
echo "Warning: failed to check pipeline status for $component: $result" >&2
Expand All @@ -46,10 +106,11 @@ for component in "${COMPONENTS[@]}"; do
blocking=$(echo "$blocking" | jq --argjson e "$entry" '. + [$e]')
continue
fi

latest=$(echo "$result" | jq '.latest')
completion=$(echo "$result" | jq -r '.latest.completionTime // "null"')

# A pipeline is running when latest is non-null and has no completionTime
# Check 1: block if a pipeline is currently running (no completionTime on latest)
if [[ "$latest" != "null" && "$completion" == "null" ]]; then
reason=$(echo "$result" | jq -r '.latest.reason // "Running"')
pipeline=$(echo "$result" | jq -r '.latest.name // "unknown"')
Expand All @@ -59,6 +120,31 @@ for component in "${COMPONENTS[@]}"; do
--arg reason "$reason" \
'{component: $component, pipeline: $pipeline, reason: $reason}')
blocking=$(echo "$blocking" | jq --argjson e "$entry" '. + [$e]')
# Don't also run check 2 — component is already blocked.
continue
fi

# Check 2: block if the last on-push pipeline on the target branch failed.
# Requires --repo and --branch; skipped when omitted.
if [[ -n "$GH_REPO" && -n "$GH_BRANCH" ]]; then
conclusion=$(gh_last_push_conclusion "$GH_REPO" "$GH_BRANCH" "$component")
if [[ "$conclusion" == "error" ]]; then
entry=$(jq -n \
--arg component "$component" \
--arg pipeline "GitHub API" \
--arg reason "Failed to query on-push pipeline status for branch ${GH_BRANCH}" \
'{component: $component, pipeline: $pipeline, reason: $reason}')
blocking=$(echo "$blocking" | jq --argjson e "$entry" '. + [$e]')
elif [[ "$conclusion" != "success" && "$conclusion" != "unknown" ]]; then
check_name="Red Hat Konflux / ${component}-on-push"
entry=$(jq -n \
--arg component "$component" \
--arg pipeline "$check_name" \
--arg reason "Last on-push pipeline did not succeed on branch ${GH_BRANCH} (GitHub check: ${check_name}, conclusion: ${conclusion})" \
'{component: $component, pipeline: $pipeline, reason: $reason}')
blocking=$(echo "$blocking" | jq --argjson e "$entry" '. + [$e]')
fi
# "unknown" → no on-push run found in last 20 commits → don't block
fi
done

Expand Down
51 changes: 38 additions & 13 deletions scripts/process-konflux-prs/get-pr-status.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# (enterprise-contract checks with NEUTRAL conclusion are excluded)
# build_checks_failed: array of names of build checks that failed (FAILURE conclusion)
# pending_checks: integer count of checks still IN_PROGRESS, QUEUED, or PENDING
# checks: raw array of all checks (name, status, conclusion, details_url); nulls filtered out
# checks: raw array of all checks (name, status, conclusion); nulls filtered out

set -euo pipefail

Expand Down Expand Up @@ -60,38 +60,62 @@ fi
# Get PR details
pr_data=$(gh pr view "$PR_NUMBER" \
--repo "$REPO_URL" \
--json number,title,state,labels,statusCheckRollup,baseRefName)
--json number,title,state,labels,statusCheckRollup,baseRefName,headRefOid)

# All checks, with null entries filtered out
# Normalize all checks: both CheckRun (name/conclusion) and StatusContext (context/state).
# StatusContext objects have no "name" field — they use "context" instead, and "state"
# instead of "conclusion". Mapping: FAILURE/ERROR -> FAILURE, PENDING/EXPECTED -> IN_PROGRESS.
checks=$(echo "$pr_data" | jq '[(.statusCheckRollup // [])[] |
select(.name != null) |
{
name: .name,
status: .status,
conclusion: (.conclusion // ""),
details_url: .detailsUrl
}]')
if .name != null then
{
name: .name,
status: .status,
conclusion: (.conclusion // "")
}
elif .context != null then
{
name: .context,
status: (if (.state == "PENDING" or .state == "EXPECTED") then "IN_PROGRESS" else "COMPLETED" end),
conclusion: (if .state == "SUCCESS" then "SUCCESS"
elif (.state == "FAILURE" or .state == "ERROR") then "FAILURE"
elif (.state == "PENDING" or .state == "EXPECTED") then ""
else .state end)
}
else empty end]')

# Labels: array of strings
labels=$(echo "$pr_data" | jq '[(.labels // [])[].name]')

# Build pipeline checks only: "Red Hat Konflux / {component}-on-pull-request[…]"
# Enterprise-contract checks (containing "enterprise-contract") are excluded —
# they return NEUTRAL when not required, which is not a failure.
# Enterprise-contract checks are excluded ONLY when their conclusion is NEUTRAL
# (not enforced on this branch). A FAILURE conclusion from an enterprise-contract
# check is a real failure and must block the PR.
build_checks=$(echo "$checks" | jq '[.[] |
select(.name | startswith("Red Hat Konflux / ")) |
select(.name | test("-on-pull-request")) |
select(.name | contains("enterprise-contract") | not)]')
select((.name | contains("enterprise-contract")) and (.conclusion == "NEUTRAL") | not)]')

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.

Behavioral change: enterprise-contract FAILURE now blocks PRs.

Previously, all enterprise-contract checks were unconditionally excluded from build_checks. Now only NEUTRAL conclusions are excluded, so a FAILURE result from an enterprise-contract check will cause build_checks_passed = false and block labelling/merging.

The comment here explicitly documents this as intentional. Flagging for visibility: on branches where the enterprise-contract check actively runs and can produce FAILURE (rather than NEUTRAL), any ec failure will now permanently hold up the PR until resolved. Please confirm this is the intended operational behavior before merging.

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.

Yes, that was intentional.
The thing is that the EC is flagged neutral, whether it makes warnings or errors, and only on our "devel" branch.
For the other branches, it is passing for warnings, and failing for actual failures.
And the problem is that we have a LOT of warnings.

At this point, it is easier for me to ignore "Neutral" state on devel, rather than trying to find out if there's an error within it, and I'm still monitoring the EC failures in the Konflux console.
For the other branches though, an error is a real red flag that this script can catch.


# Non-build checks that failed: anything with FAILURE conclusion that is not a
# Konflux build pipeline check. These block merging and require developer attention.
other_checks_failed=$(echo "$checks" | jq '[.[] |
select(
(.name | startswith("Red Hat Konflux / ") and test("-on-pull-request") and (contains("enterprise-contract") | not))
| not
) |
select(.conclusion == "FAILURE") |
.name]')

echo "$pr_data" | jq \
--argjson checks "$checks" \
--argjson labels "$labels" \
--argjson build_checks "$build_checks" \
--argjson other_checks_failed "$other_checks_failed" \
'{
number: .number,
title: .title,
state: .state,
base_branch: .baseRefName,
head_sha: .headRefOid,
labels: $labels,
has_ok_to_test: ($labels | any(. == "ok-to-test")),
has_lgtm: ($labels | any(. == "lgtm")),
Expand All @@ -102,6 +126,7 @@ echo "$pr_data" | jq \
(map(.conclusion == "SUCCESS") | all)),
build_checks_failed: [$build_checks[] |
select(.conclusion == "FAILURE") | .name],
other_checks_failed: $other_checks_failed,
pending_checks: ([$checks[] |
select(.status == "IN_PROGRESS" or
.status == "QUEUED" or
Expand Down
22 changes: 18 additions & 4 deletions scripts/process-konflux-prs/list-prs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,16 @@ while [[ $# -gt 0 ]]; do
PR_TYPE="nudge"
shift
;;
--test-fbc)
PR_TYPE="test-fbc"
shift
;;
--repo)
REPO_FILTER="$2"
shift 2
;;
*)
echo "Usage: $0 [--mintmaker|--nudge] [--repo REPO_NAME]" >&2
echo "Usage: $0 [--mintmaker|--nudge|--test-fbc] [--repo REPO_NAME]" >&2
exit 1
;;
esac
Expand All @@ -57,9 +61,18 @@ is_mintmaker_pr() {
[[ "$title" == "chore(deps): update registry.access.redhat.com/ubi9"* ]] && return 0
[[ "$title" == "chore(deps): update registry.access.redhat.com/ubi10"* ]] && return 0
[[ "$title" == "chore(deps): update registry.redhat.io/rhel9/rhel-bootc"* ]] && return 0
[[ "$title" == "chore(deps): update registry.redhat.io/ubi9"* ]] && return 0
[[ "$title" == "Update registry.access.redhat.com/ubi9"* ]] && return 0
[[ "$title" == "Update registry.access.redhat.com/ubi10"* ]] && return 0
[[ "$title" == "Update registry.redhat.io/rhel9/rhel-bootc"* ]] && return 0
[[ "$title" == "Update registry.redhat.io/ubi9"* ]] && return 0
[[ "$title" == "chore(deps): refresh rpm lockfiles"* ]] && return 0
Comment thread
c3d marked this conversation as resolved.
[[ "$title" == "Update podvm-payload/kata-containers digest"* ]] && return 0
[[ "$title" == "chore(deps): update podvm-payload/kata-containers"* ]] && return 0
[[ "$title" == "Update podvm-payload/guest-components digest"* ]] && return 0
[[ "$title" == "chore(deps): update podvm-payload/guest-components"* ]] && return 0
[[ "$title" == "Update config/peerpods/podvm/cloud-api-adaptor digest"* ]] && return 0
[[ "$title" == "chore(deps): update config/peerpods/podvm/cloud-api-adaptor"* ]] && return 0
return 1
}

Expand Down Expand Up @@ -95,10 +108,11 @@ for repo_name in "${!REPOS[@]}"; do
# Determine PR type
pr_type=""

# Check if it's a Nudge PR
# Check if it's a Nudge PR (or the test-fbc subset of nudge PRs)
if echo "$labels" | grep -q "konflux-nudge"; then
# Skip bundle update PRs
if [[ "$title" != "chore(deps): update osc-operator-bundle"* ]]; then
if [[ "$title" == "chore(deps): update osc-operator-bundle"* ]]; then
pr_type="test-fbc"
else
pr_type="nudge"
fi
# Check if it's a Mintmaker PR
Expand Down
22 changes: 16 additions & 6 deletions scripts/process-konflux-prs/merge-pr.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
#!/bin/bash
# Merge a PR using squash strategy
# Usage: ./merge-pr.sh --repo REPO_NAME --pr PR_NUMBER
# Merge a PR using merge strategy
# Usage: ./merge-pr.sh --repo REPO_NAME --pr PR_NUMBER [--head-sha SHA]
#
# When --head-sha is provided, the merge is rejected if the PR's HEAD
# has changed since the snapshot (prevents merging unchecked commits).
#
# Returns JSON: {success, repo, pr, message}

set -euo pipefail

REPO_URL=""
PR_NUMBER=""
HEAD_SHA=""

# Repository mapping
declare -A REPOS=(
Expand All @@ -34,6 +38,10 @@ while [[ $# -gt 0 ]]; do
PR_NUMBER="$2"
shift 2
;;
--head-sha)
HEAD_SHA="$2"
shift 2
;;
*)
echo "Usage: $0 --repo REPO_NAME --pr PR_NUMBER" >&2
exit 1
Expand All @@ -48,10 +56,12 @@ fi

REPO_NAME=$(basename "$REPO_URL")

if gh pr merge "$PR_NUMBER" \
--repo "$REPO_URL" \
--merge \
--delete-branch 2>/dev/null; then
MERGE_ARGS=("$PR_NUMBER" --repo "$REPO_URL" --merge --delete-branch)
if [[ -n "$HEAD_SHA" ]]; then
MERGE_ARGS+=(--match-head-commit "$HEAD_SHA")
fi

if gh pr merge "${MERGE_ARGS[@]}" 2>/dev/null; then
echo "{\"success\": true, \"repo\": \"$REPO_NAME\", \"pr\": $PR_NUMBER, \"message\": \"merged\"}" | jq '.'
else
MSG=$(gh pr view "$PR_NUMBER" --repo "$REPO_URL" --json state,mergeStateStatus \
Expand Down
Loading