plugins/ci/skills/revert-pr/SKILL.md
Git revert workflow and Revertomatic PR template for reverting merged PRs
npx skillsauth add openshift-eng/ai-helpers revert-prInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill provides the detailed git revert workflow and the exact PR body template used by Revertomatic for reverting merged pull requests that break CI or nightly payloads.
Use this skill when:
--draft: When set, create the revert PR as a draft (gh pr create --draft). Used by the experimental revert workflow to open experimental revert PRs that may be closed if the suspect is cleared.--context: When the caller passes context directly (e.g., from an autonomous pipeline that already has all context in memory), skip the JIRA lookup in Step 5. The provided context string is used as-is for the {CONTEXT} template variable.GitHub CLI (gh): Installed and authenticated
gh auth statusGit: Installed and configured
which gitRepository Access: User must have push access to their fork of the target repository
Use the gh CLI to fetch all necessary details about the PR being reverted:
# Fetch PR details as JSON
pr_data=$(gh pr view "$PR_URL" --json number,title,author,mergeCommit,baseRefName,state)
# Extract fields
pr_number=$(echo "$pr_data" | jq -r '.number')
pr_title=$(echo "$pr_data" | jq -r '.title')
pr_author=$(echo "$pr_data" | jq -r '.author.login')
merge_sha=$(echo "$pr_data" | jq -r '.mergeCommit.oid')
base_branch=$(echo "$pr_data" | jq -r '.baseRefName')
pr_state=$(echo "$pr_data" | jq -r '.state')
Validation:
pr_state must be MERGED. If the PR is not merged, abort with an error.merge_sha must not be empty or null.Parse the PR URL to determine owner and repository:
# From URL like https://github.com/openshift/kubernetes/pull/1703
# Extract: owner=openshift, repo=kubernetes
# Get authenticated user
gh_user=$(gh api user --jq '.login')
# Check if fork exists; create one if not
if ! gh api "repos/$gh_user/$repo" &>/dev/null; then
echo "Creating fork of $owner/$repo..."
gh repo fork "$owner/$repo" --clone=false
# Wait for fork to become available
sleep 5
fi
If no local repository is available:
# Clone upstream repo
git clone -b "$base_branch" "https://github.com/$owner/$repo.git" /tmp/revert-workdir
cd /tmp/revert-workdir
# Rename the default remote to 'upstream' so later steps can reference it consistently
git remote rename origin upstream
# Add the user's fork as the 'fork' remote (used for pushing the revert branch)
git remote add fork "[email protected]:$gh_user/$repo.git"
If using an existing local clone, ensure upstream points to the canonical repo and fork points to the user's fork:
# Verify remotes: 'upstream' must point to the canonical repo, 'fork' to the user's fork
git remote -v
# If 'upstream' is missing but 'origin' points to the canonical repo, rename it:
# git remote rename origin upstream
# If 'fork' is missing, add it:
# git remote add fork "[email protected]:$gh_user/$repo.git"
If --context was provided: Skip the JIRA lookup entirely. Use the provided context string as-is for the {CONTEXT} template variable and proceed to Step 6. The caller has already gathered all necessary context.
Otherwise, when a JIRA ticket is provided, use the fetch-jira-issue skill to automatically gather context about what broke and which jobs need verification before unreverting.
# Path to the fetch-jira-issue script
jira_script="plugins/ci/skills/fetch-jira-issue/fetch_jira_issue.py"
# Fetch JIRA issue details
jira_data=$(python3 "$jira_script" "$JIRA" --format json 2>/dev/null)
Extract context from the JIRA issue:
From the JSON output, examine the summary, comments, and linked_prs fields to determine:
What broke (for the {CONTEXT} template variable):
e2e-aws, e2e-gcp, nightly, payload, or release stream URLsVerification jobs (for the unrevert instructions):
e2e-aws, e2e-gcp, e2e-metal-ipi, e2e-ovn, etc.# Example: Extract context from JIRA data
jira_summary=$(echo "$jira_data" | jq -r '.summary')
jira_comments=$(echo "$jira_data" | jq -r '.comments[].body')
# Look for job names and payload URLs in the summary and comments
# to build the CONTEXT and VERIFICATION variables
Fallback: If the JIRA lookup fails (no token, network error, or insufficient detail in the ticket), ask the user interactively:
If the user also provided inline context as arguments, combine it with the JIRA-derived context.
YOU MUST ALWAYS DO THIS. Before creating the revert, check recent commits in the repository to determine if it uses a special commit/PR title prefix convention. Skipping this step will cause verify-commits CI jobs to fail.
# Check the last 20 commit subjects on the base branch
git log "upstream/$base_branch" --oneline -20
UPSTREAM carry convention: Some repositories (notably openshift/kubernetes and other repos carrying upstream patches) use the prefix format UPSTREAM: <tag>: in commit messages. Common tags include <carry>, <drop>, and upstream cherry-pick numbers like <12345>.
Detection logic:
UPSTREAM: <tag>: format, the revert must follow the same convention.UPSTREAM: <carry>: Revert "ORIGINAL_TITLE" because REASON
For example:
UPSTREAM: <carry>: Revert "UPSTREAM: 12345: Fix kubelet crash" because it broke e2e-aws jobs
Store the detected convention for use in Steps 7 and 9.
# Fetch latest from upstream
git fetch upstream
# Create revert branch from base branch
revert_branch="revert-${pr_number}-$(date +%s%3N)"
git checkout -b "$revert_branch" "upstream/$base_branch"
# Revert the merge commit (first parent = base branch)
git revert -m1 --no-edit "$merge_sha"
Important: The -m1 flag tells git to revert relative to the first parent of the merge commit, which is the base branch. This effectively undoes the changes introduced by the PR.
If the UPSTREAM convention was detected in Step 6, you MUST amend the revert commit message to include the appropriate UPSTREAM: <tag>: prefix. The default git revert message (Revert "...") will fail verify-commits CI checks.
Determine the appropriate tag by looking at the commit being reverted and the repo conventions (e.g., <carry>, <drop>).
# Set the tag based on the convention detected in Step 6
upstream_tag="carry" # or "drop", or a cherry-pick number — based on context
# Get the current commit message
current_msg=$(git log -1 --format=%B)
# Prepend the UPSTREAM: <tag>: prefix to the first line
amended_msg=$(echo "$current_msg" | sed "1s/^/UPSTREAM: <$upstream_tag>: /")
# Amend the commit
git commit --amend -m "$amended_msg"
This transforms the commit message from:
Revert "Merge pull request #638 from author/branch"
to:
UPSTREAM: <carry>: Revert "Merge pull request #638 from author/branch"
If git revert fails with conflicts, determine the best strategy:
Strategy A: Resolve simple/obvious conflicts
Use this when conflicts are trivial and unambiguous:
To resolve:
git diff and git statusgit addgit revert --continuegit commit --amend -m "$(git log -1 --format=%B)
Note: Merge conflicts in {FILE_LIST} were resolved manually.
Conflicts were trivial ({DESCRIPTION}, e.g. 'generated file regeneration', 'one-line context change')."
Strategy B: Revert dependent commits
Use this when conflicts are non-trivial, meaning later commits depend on the changes introduced by the PR being reverted:
# List commits after the merge commit on the base branch
git log --oneline "$merge_sha"..upstream/"$base_branch"
git revert --abort# Revert dependent commits first (newest to oldest)
git revert --no-edit <dependent_sha_newest>
git revert --no-edit <dependent_sha_next>
# ... then revert the original target
git revert -m1 --no-edit "$merge_sha"
Note: The following dependent commits were also reverted because
they conflict with or depend on the original change:
- <sha1> <title1>
- <sha2> <title2>
After conflict resolution (either strategy), push to the fork:
# Push to fork
git push fork "$revert_branch:$revert_branch"
After the revert PR is created, determine which CI jobs need /override commands:
# Get the revert PR's head SHA
pr_sha=$(gh pr view "$revert_pr_url" --json headRefOid --jq '.headRefOid')
# List all status contexts
statuses=$(gh api "repos/$owner/$repo/statuses/$pr_sha" --jq '.[].context' | sort -u)
Filter out unoverridable jobs - these are fast-running quality gates that should always pass:
Jobs matching the following pattern should NOT be overridden:
.*(unit|lint|images|verify|tide|verify-deps|fmt|vendor|vet)$
Format remaining jobs as override commands:
/override ci/prow/e2e-aws
/override ci/prow/e2e-gcp-ovn
/override ci/prow/e2e-metal-ipi
...
PR Title Format depends on the commit convention detected in Step 6:
Standard repositories:
{JIRA}: Revert #{PR_NUMBER} "{ORIGINAL_TITLE}"
Example: TRT-9999: Revert #1703 "Fix kubelet crash on restart"
UPSTREAM carry repositories (e.g., openshift/kubernetes):
{JIRA}: UPSTREAM: <tag>: Revert "{ORIGINAL_TITLE}"
Example: TRT-9999: UPSTREAM: <carry>: Revert "UPSTREAM: 12345: Fix kubelet crash"
PR Body - Revertomatic Template:
This is the exact template format used by Revertomatic. Use this format precisely:
Reverts #{ORIGINAL_PR_NUMBER} ; tracked by {JIRA_ISSUE}
Per [OpenShift policy](https://github.com/openshift/enhancements/blob/master/enhancements/release/improving-ci-signal.md#quick-revert), we are reverting this breaking change to get CI and/or nightly payloads flowing again.
{CONTEXT}
To unrevert this, revert this PR, and layer an additional separate commit on top that addresses the problem. Before merging the unrevert, please run these jobs on the PR and check the result of these jobs to confirm the fix has corrected the problem:
{OVERRIDE_COMMANDS}
CC: @{ORIGINAL_AUTHOR}
Template Variables:
{ORIGINAL_PR_NUMBER}: The PR number being reverted (e.g., 1703){JIRA_ISSUE}: The JIRA ticket tracking the revert (e.g., TRT-9999){CONTEXT}: Explanation of why the revert is needed, derived from the JIRA ticket (Step 5) or provided by the user (e.g., "This PR broke all e2e-aws jobs on the 4.18 nightly payload at https://amd64.ocp.releases.ci.openshift.org/..."){OVERRIDE_COMMANDS}: The list of /override commands for CI jobs that need to be bypassed{ORIGINAL_AUTHOR}: GitHub username of the original PR authorCreate the PR:
gh pr create \
--repo "$owner/$repo" \
--base "$base_branch" \
--head "$gh_user:$revert_branch" \
--title "$jira: Revert #$pr_number \"$pr_title\"" \
--body "$rendered_body"
If --draft was set, add the --draft flag to the gh pr create command:
gh pr create \
--repo "$owner/$repo" \
--base "$base_branch" \
--head "$gh_user:$revert_branch" \
--title "$jira: Revert #$pr_number \"$pr_title\"" \
--body "$rendered_body" \
--draft
After generating override commands in Step 8, return them to the user as a list. Do NOT post them as a comment on the PR automatically. The user can copy-paste them manually if needed.
Note: Override commands may need to be posted after CI jobs have started running and reported their status contexts. If no statuses are available yet, inform the user they can check the PR later for required overrides.
Error: PR #1703 is in state OPEN, not MERGED.
Only merged PRs can be reverted with this command.
Error: Could not find merge commit SHA for PR #1703.
The PR may have been squash-merged or rebased.
For squash-merged PRs, the merge commit SHA is the squash commit itself. Use gh pr view to get the correct SHA.
Error: git revert -m1 failed due to conflicts.
See Step 7 for the two conflict resolution strategies:
Warning: Fork not ready after creation. Waiting...
If the fork was just created, retry with exponential backoff (up to ~30 seconds).
Note: No CI status contexts found on the revert PR yet.
Override commands will be available after CI jobs start running.
# Input
PR_URL="https://github.com/openshift/kubernetes/pull/1703"
JIRA="TRT-9999"
CONTEXT="This PR broke all jobs on https://amd64.ocp.releases.ci.openshift.org/releasestream/4.15.0-0.nightly/release/4.15.0-0.nightly-2023-10-03-025546"
VERIFY="Run e2e-aws and e2e-gcp jobs"
# Result: Creates PR with title
# TRT-9999: Revert #1703 "UPSTREAM: 12345: Fix kubelet crash on restart"
Reverts #1703 ; tracked by TRT-9999
Per [OpenShift policy](https://github.com/openshift/enhancements/blob/master/enhancements/release/improving-ci-signal.md#quick-revert), we are reverting this breaking change to get CI and/or nightly payloads flowing again.
This PR broke all jobs on https://amd64.ocp.releases.ci.openshift.org/releasestream/4.15.0-0.nightly/release/4.15.0-0.nightly-2023-10-03-025546
To unrevert this, revert this PR, and layer an additional separate commit on top that addresses the problem. Before merging the unrevert, please run these jobs on the PR and check the result of these jobs to confirm the fix has corrected the problem:
/override ci/prow/e2e-aws /override ci/prow/e2e-gcp-ovn /override ci/prow/e2e-metal-ipi
CC: @originalauthor
git revert -m1 flag is critical: it specifies the first parent (base branch) as the mainline for the revertrevert-{number}-{timestamp_millis} to avoid collisions with existing branches/ci:revert-pr - The user-facing command that uses this skill (plugins/ci/commands/revert-pr.md)fetch-jira-issue - Fetches JIRA issue details for automatic context extraction (plugins/ci/skills/fetch-jira-issue/SKILL.md)testing
Snapshot OpenShift payload data (release controller, PR diffs, comments, CI jobs, JUnit results, regression tracking) to a local directory for offline analysis
research
Shared engine for analyzing Jira issue activity and generating status summaries
tools
This skill should be used before any Snowflake command to verify MCP connectivity, guide users through access provisioning, and set the session context. Invoke this skill proactively whenever a command needs Snowflake data access.
development
Analyze a payload snapshot to identify root causes of blocking job failures, score candidate PRs, and produce an HTML report with revert recommendations