| name | backport-failed |
| description | Create a manual backport to recover from a Patchback auto-backport failure. |
/backport-failed — Manual recovery for failed patchback backports
When patchback (the auto-backport bot used by aio-libs and similar orgs) fails to cherry-pick a merged PR onto a stable branch, it leaves a comment like:
Backport to 3.13: 💔 cherry-picking failed — conflicts found
❌ Failed to cleanly apply <sha> on top of patchback/backports/3.13/<sha>/pr-<num>
This skill produces the manual backport PRs that should have been created, matching patchback's exact shape so the PR list stays uniform.
Arguments passed: $ARGUMENTS — usually a PR number (e.g. 12581). If empty, ask the user for it via AskUserQuestion.
The exact patchback PR shape
Every backport PR this skill opens must match this shape (study PRs authored by patchback[bot] — e.g. aio-libs/aiohttp#12574 — for the canonical form):
- Title:
[PR #<orig_num>/<short_sha> backport][<branch>] <original PR title>
<short_sha> is the first 8 chars of the merge commit on master
<branch> is the target branch, e.g. 3.13, 3.14
- Body first line:
**This is a backport of PR #<orig_num> as merged into master (<full_sha>).**
- Body remainder: the original PR body verbatim (including the HTML template comments and the unchecked checklist items as patchback preserves them — copy what's on the original PR)
- Base branch: the stable branch (
3.13, 3.14, etc.)
- Head branch on fork:
patchback/backports/<branch>/<full_sha>/pr-<orig_num>
Do not add a "Drafted with Claude Code" footer, do not add Co-Authored-By, do not edit or summarize the original body. The goal is byte-for-byte indistinguishability from a successful patchback PR.
Procedure
1. Resolve the PR number and gather metadata
If $ARGUMENTS is empty, ask the user for the PR number with AskUserQuestion. Then in parallel:
gh pr view <num> --repo <owner/repo> --json number,title,body,mergeCommit,headRepositoryOwner,baseRefName,labels,url
gh pr view <num> --repo <owner/repo> --comments
Detect the repo from the working directory's upstream remote (git remote get-url upstream). If there is no upstream remote, ask the user which remote points at the canonical repo.
Extract:
orig_num — the PR number
orig_title — the PR title (use as-is, do not modify)
orig_body — the PR body (use as-is, do not modify, do not strip HTML comments)
full_sha — the merge commit SHA on master (from mergeCommit.oid)
short_sha — first 8 chars of full_sha
2. Find the failed target branches
From the comments output, find each patchback comment that says "💔 cherry-picking failed — conflicts found" and extract the target branch from the heading (Backport to <branch>:).
Sanity check against PR labels: patchback is driven by labels like backport-3.13, backport-3.14. The set of failed branches should be a subset of those labels. If a backport label has no patchback failure comment and no existing backport PR, that target probably succeeded automatically — skip it.
To confirm a branch genuinely needs a manual backport, check whether a successful backport PR already exists:
gh pr list --repo <owner/repo> --search "[PR #<orig_num>/<short_sha> backport]" --state all
If a PR with the expected title already exists for that branch, skip it.
3. Verify the working tree is clean enough to checkout branches
git status --porcelain
If there are staged/unstaged changes to tracked files, stop and tell the user. Untracked files are fine — patchback's workflow doesn't touch them.
Remember the current branch so you can return to it at the end:
git rev-parse --abbrev-ref HEAD
4. For each failed branch, create the backport
Run these steps sequentially per branch — never parallelize across target branches (they share the working tree).
git fetch upstream
git checkout -b patchback/backports/<branch>/<full_sha>/pr-<orig_num> upstream/<branch>
git cherry-pick -x <full_sha>
If the commit is a merge commit (git cat-file -p <full_sha> | grep -c '^parent ' returns > 1), use git cherry-pick -m1 -x <full_sha> instead. For squash-merged PRs (the common case on aio-libs) it's a single-parent commit, so plain -x works.
Resolving conflicts
If cherry-pick reports conflicts:
- Run
git status to list conflicted files.
- For each file, Read it, locate the
<<<<<<</=======/>>>>>>> markers, and resolve.
- Conflict-resolution principles (in order):
- Preserve the intent of the original PR — the change being backported is the source of truth for what should land.
- Adapt to the stable branch's existing context. The conflict usually means surrounding code differs (e.g. a function got renamed on master but not on 3.13). Apply the PR's logical change to the stable branch's version of the code.
- For news fragments (
CHANGES/*.rst) the file usually doesn't exist on the stable branch yet — just take the master version.
- If you cannot confidently resolve a conflict, stop and ask the user. Do not guess. Show the conflict markers and your proposed resolution, and let them confirm.
git add <file> for each resolved file.
git cherry-pick --continue (use a non-interactive editor: GIT_EDITOR=true git cherry-pick --continue to keep the cherry-pick's default message which already contains the (cherry picked from commit ...) trailer from -x).
Push to the user's fork
The user's fork remote is conventionally origin for aio-libs contributors (or named after their GitHub handle). Detect it:
git remote -v | grep -E "(fetch)" | grep -v upstream
If multiple non-upstream remotes exist, prefer one matching the GitHub user from gh api user --jq .login. Ask the user if ambiguous.
git push <fork_remote> patchback/backports/<branch>/<full_sha>/pr-<orig_num>
Open the backport PR
Use a HEREDOC so the body is preserved exactly. The body of the new PR is the original PR's body, prepended with the **This is a backport...** line and a blank line:
gh pr create \
--repo <owner/repo> \
--base <branch> \
--head <fork_owner>:patchback/backports/<branch>/<full_sha>/pr-<orig_num> \
--title "[PR #<orig_num>/<short_sha> backport][<branch>] <orig_title>" \
--body "$(cat <<'EOF'
**This is a backport of PR #<orig_num> as merged into master (<full_sha>).**
<orig_body verbatim>
EOF
)"
If <orig_body> contains an EOF marker itself (rare), pick a different HEREDOC delimiter (e.g. PATCHBACK_BODY).
Report the new PR URL.
5. Return to the original branch
After all backports are done (or after stopping for user input):
git checkout <original_branch>
Leave the patchback branches in place locally — the user may want to amend them.
Output to the user
At the end, print a short summary:
- Which branches had failed backports
- For each: the new PR URL, or
skipped (already exists), or paused for user (conflict in <file>)
Keep it terse — one line per branch.
Edge cases
- No patchback failure comments found. Tell the user the PR's auto-backports look healthy and ask if they want to force a manual backport anyway.
- Cherry-pick is clean (no conflicts). Still legitimate — patchback may have failed for a transient reason. Proceed to push and PR creation as normal.
- PR was a merge commit, not a squash. Use
-m1 as noted above.
- The fork already has the branch.
git push --force-with-lease only if the user confirms (AskUserQuestion). Never --force blindly.
- News fragment filename differs. Patchback preserves whatever filename the original PR used; do not rename.
- Stable branch is end-of-life. If
gh api repos/<owner>/<repo>/branches/<branch> 404s, tell the user — the backport label may be stale.