Backport a PR to a maintenance major branch

SkillDocs & knowledge

Lets your agent copy a merged pull request onto an older maintenance branch and open a draft backport PR.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Backport a PR to a maintenance major branch skill

About this capability

Backport a merged PR to a maintenance major branch (v10 by default) in getsentry/sentry-javascript. Cherry-picks the PR's squash-merge commit onto the target branch, namespaces the commit/PR title scope (e.g. fix(core) -> fix(v10/core)), opens a draft backport PR, and documents any deviation from th

What this skill tells your AI

The instructions your AI receives, as published by getsentry/sentry-javascript in .agents/skills/backport-pr/SKILL.md and read by ahel’s review.

develop is the current major (v11). A change that also needs to ship on a still-maintained older major has to land on that major's branch too (v10 by default). This skill cherry-picks a merged develop PR onto that branch and opens a draft backport PR.

Inputs

  • PR (required): the already-merged PR on develop to backport, given as either a full GitHub URL or a bare number. gh pr view accepts both, so pass whichever the user gave through unchanged; <PR> in the commands below is that value.
  • Target major (optional, default v10): the maintenance branch to backport onto. Accept v10, 10, v9, etc. Normalize to a branch name like v10.

If no PR is given, ask for it. Do not guess.

Convention

  • Base branch = the target major branch (v10), which must already exist on origin.
  • Commit + PR title: keep the original conventional-commit prefix but namespace the scope with the major, e.g.
    • fix(core): Fix logs flush starvation -> fix(v10/core): Fix logs flush starvation
    • feat(node): Add X -> feat(v10/node): Add X
    • If the original has no scope (e.g. fix: ...), use fix(v10): ....
    • For a multi-scope title, prefix the whole group once, not each scope: fix(cloudflare,deno,node): ... -> fix(v10/cloudflare,deno,node): ....
  • PR body starts with a single line: Backport of: #<original-pr-number>. If the backported change deviates from the original in substance, append a ## Differences to the original PR section listing each deviation and why it was necessary (see step 4). A clean cherry-pick keeps the body at the single line.
  • PR is opened as a draft.
  • Working branch: branch off the target major and give it a descriptive name.
  • The changes come from the PR's squash-merge commit on develop (one commit per PR), so a single git cherry-pick normally covers the whole PR.

Steps

1. Resolve the PR and target branch

# Fetch PR metadata (title, merge commit, base branch)
gh pr view <PR> --json number,title,baseRefName,mergeCommit,state,url

Verify:

  • The PR is merged (state == "MERGED"). If not, stop and tell the user.
  • Its baseRefName is develop (or the expected parent major). If it targeted something else, confirm with the user before continuing.

Grab mergeCommit.oid — this is the squash commit to cherry-pick. Also grab number: use that bare number (not the raw input) wherever #<PR> appears below, so Backport of: reads Backport of: #18211 even when the user passed a URL.

Make sure the target branch exists and is up to date:

git fetch origin <major> develop
git rev-parse --verify origin/<major>   # errors if the branch doesn't exist

If origin/<major> doesn't exist, stop: the maintenance branch hasn't been created yet.

Then check whether the change is already on the target. A freshly cut major often still shares history with develop, so a recent PR may already be present:

git merge-base --is-ancestor <mergeCommit-oid> origin/<major> && echo "ALREADY ON <major>"

If it prints ALREADY ON, the commit is in the target's history — usually meaning nothing to backport. It's not conclusive on its own, though: a commit that was later reverted on the maintenance branch still shows as an ancestor. So treat this as a strong signal to stop and tell the user, but if you have reason to think the change was reverted, confirm the fix is actually present (e.g. git log origin/<major> -- <a changed file>, or grep for the change) before deciding. The cherry-pick in step 3 is the real backstop — it comes up empty only when the change is genuinely still applied.

2. Create the backport branch off the target major

git checkout -b <branch> origin/<major>

3. Cherry-pick the merge commit

git cherry-pick <mergeCommit-oid>
  • If git reports the pick is empty ("nothing to commit" / "the previous cherry-pick is now empty"), the change is already on the target. Run git cherry-pick --abort and stop — do not force it through with --allow-empty. This is the same situation the ancestor check in step 1 guards against, caught here for changes that landed via a different commit.
  • On conflicts: resolve them by consulting the original diff (git show <oid>). The target major may lack refactors that landed on develop, so adapt the change to the older code rather than force-porting it. Stage the resolved files with git add -u (tracked files only, so stray untracked workspace files don't get baked in), then git cherry-pick --continue. If the change can't be cleanly adapted, stop and surface the conflict to the user instead of guessing. Keep a running note of every adaptation you make here — which file, what you changed relative to the original, and why the target major needed it. Step 6 turns these notes into the PR body, and reconstructing them after the fact is unreliable.
  • If the PR was not squash-merged (multiple commits, e.g. a merge commit), cherry-pick each relevant commit in order, or use git cherry-pick -m 1 <merge-oid> for a merge commit.

4. Build and verify

Run the repo's pre-commit checks. Do this before finalizing the commit in step 5, because yarn format writes changes to the working tree — those fixes must end up inside the backport commit, not left dangling after it (otherwise you'd push an unformatted tree and CI would fail on a commit that doesn't match your local state).

yarn format
yarn lint:fix
yarn build:dev

Use lint:fix, not lint — plain yarn lint only reports, so auto-fixable issues would otherwise survive to fail CI.

Run tests scoped to the touched packages when possible (full yarn test if unsure). If the target major's toolchain differs and a check fails for reasons unrelated to the change, note it for the user rather than silently skipping.

5. Finalize the commit (fold in verification changes)

First stage the format/lint fixes. Use git add -u so only tracked files the cherry-pick and verification touched are staged, not unrelated local edits — sanity-check with git status first if yarn format may have reformatted files outside the backport.

git add -u

Then finalize, depending on how step 3 went:

Single squash commit (the usual case) — amend HEAD to both namespace the subject scope and fold in the staged fixes. The message is the namespaced title plus the one-line Backport of: body (this replaces the squash-merge body, matching the convention above). Do not add a Co-Authored-By line — the backport commit mirrors an existing commit, not new authored work.

Build the title as in the convention: <prefix>(<major>/<scope>): when the original had a scope, or <prefix>(<major>): when it didn't (never emit an empty <major>/).

git commit --amend -m "<namespaced-title>" -m "Backport of: #<PR>"

Example subject: fix(v10/core): Fix logs flush timeout starvation with continuous logging

Multiple commits (non-squash merge) — leave the individual commit messages as-is; the namespaced title lives on the PR (step 7), not on each commit. Just fold the staged fixes into HEAD without rewording:

git commit --amend --no-edit

Confirm the tree is clean so nothing is left uncommitted before you push:

git status --porcelain   # expect no output

6. Diff the backport against the original PR

Even a cherry-pick that reported no conflicts can end up different — yarn format/lint:fix on the older toolchain, dropped hunks, adaptations you made in step 3. Compare the final commit against the original before writing the PR body, rather than trusting your memory of step 3:

git show <mergeCommit-oid> --stat
git show HEAD --stat            # file lists should match

diff <(git show <mergeCommit-oid>) <(git show HEAD)

Ignore noise that carries no meaning: commit hashes/headers, hunk line offsets, surrounding context lines, and the reworded commit message. What counts as a difference is a change in what the patch does — a file added or missing, a hunk dropped or added, changed code or test expectations, an API adapted to the older major, a version/changelog entry that only exists on one branch.

If the only differences are noise, the PR body stays the single Backport of: line. Otherwise draft a section like:

Backport of: #<PR>

## Differences to the original PR

- `packages/core/src/foo.ts`: used `getClient()` instead of `getCurrentScope().getClient()` — v10 has no `getClient()` on the scope.
- Dropped the `bar.test.ts` case for `baz()`; that helper doesn't exist on v10.

One bullet per deviation: what changed, and why the target major required it. Keep it factual — this is the reviewer's diff-of-the-diff, not a summary of the original PR.

If a difference means the backport doesn't fully deliver the original fix, say so explicitly in the PR body and to the user — that's a review decision, not a footnote.

7. Push and open the draft PR

The Backport of: #<PR> line references the original PR, so GitHub cross-links the two automatically — no separate comment needed.

git push -u origin <branch>

gh pr create \
  --draft \
  --base <major> \
  --title "<namespaced-title>" \
  --body "Backport of: #<PR>"

When step 6 produced a differences section, pass the multi-line body via --body-file instead (write it to a scratch file first) so the markdown survives intact.

Notes

  • Never push directly to develop, master, or the major branch. Work only on your backport branch and open a PR.
  • One PR per backport. If asked to backport several PRs, repeat the whole flow per PR (each gets its own branch and draft PR).
  • If asked to backport to multiple majors at once (e.g. v10 and v9), do them as separate branches/PRs, each based off its own origin/<major>.

Signals

GitHub stars
9k
Forks
2k
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
backport-pr
Source
github.com/getsentry/sentry-javascript