
backport
✓ Official★ 197by microsoft · part of microsoft/vscode-cosmosdb
Backport changes (current branch, a PR, a branch, or specific commits/SHAs) onto a target branch (typically a release branch like `rel/*`). Use when the user says "backport this", "backport PR ...", "cherry-pick to <branch>", or asks to port commits to another branch. Handles stashing uncommitted work, branch creation, cherry-picking with conflict handling, pushing, and opening a PR.
This is the playbook your agent receives when the skill activates — you don't need to read it to use the skill, but it's here to audit before installing.
Backport Skill
Create a backport pull request that applies changes from a source (current branch, a PR, another branch, or specific commits) onto a target branch — interactively, from the local workspace. The target is typically a release branch (e.g. rel/0.32), but any existing branch is allowed except the source's own base branch (a backport onto the same base is a no-op).
Inputs to resolve
Before running any git commands, determine:
- Source — one of:
- Current branch (default if the user says "backport this" without specifying).
- PR number (e.g. "backport PR #1234").
- Branch name.
- Commit SHA(s) or range.
- Target branch — any existing branch on
origin. Typical case is a release branch (rel/*). If the user did not specify, list candidates withgit branch -r --list 'origin/rel/*'first; if none match or the user wants something else, fall back togit branch -rand ask. Refuse the source's own base branch (e.g. if the PR or current branch already targetsmain, refusemain; if it targetsrel/0.32, refuserel/0.32) — a backport onto the same base is a no-op. Verify the target exists onoriginbefore proceeding. - Squash — only if the user explicitly says "squash". Default: no squash.
If anything is ambiguous, ask once before touching the working tree.
Workflow
Execute these phases in order. Stop and report on any error.
Phase A — Pre-flight & safety
- Verify tooling:
git --version,gh --version,gh auth status. Ifghis missing or unauthenticated, abort with a clear message — do not continue. Cloud-agent override: when running as the GitHub.com cloud agent (see "Running as the GitHub cloud agent" below), a failinggh auth statusmust not abort the backport — the git work uses the platform's credential helper, notgh. See that section. - Capture state: original branch (
git rev-parse --abbrev-ref HEAD),git status --porcelain, list of unpushed commits. - If source ≠ current branch and the working tree is dirty (including untracked files):
- Run
git stash push -u -m "backport-skill autostash <ISO-timestamp>". - Record the resulting stash ref (
git stash list -1 --format=%gd) so it can be restored later.
- Run
- If source = current branch: do not stash. Warn the user if there are uncommitted changes that won't be included, and confirm before proceeding.
git fetch origin <target>. Abort if the target branch does not exist onorigin.
Phase B — Resolve the commit list
- PR source:
gh pr view <num> --json number,title,body,headRefName,baseRefName,mergeCommit,commits,state. If the command fails (non-zero exit, PR not found, or insufficient permissions), abort with a clear error message showing the PR number and thegherror output. Then branch onstate:MERGED: prefer the squash-merge commit if the PR was squash-merged; otherwise use the listed commits in order.OPEN: warn that backporting an unmerged PR may include incomplete or intermediate work, and confirm with the user before proceeding. Use the listed commits in order.CLOSED(not merged): legitimate but unusual. Warn the user that the PR was closed without merging (the work may have been abandoned, superseded, or rewritten elsewhere) and confirm before proceeding. Use the listed commits in order, or offer squash-and-reapply if the user prefers a single commit.
- Branch source:
git log --reverse --format=%H origin/<target>..<branch>. - SHA(s) / range: use as given (validate with
git cat-file -e <sha>).
Show the resolved list (count + short log) to the user and confirm before continuing.
Phase C — Create the backport branch
- Branch name:
backport/<id>-to-<target-slug>where:- Prefix is
backport/for local runs (the default — this skill running in a VS Code workspace). Use thecopilot/prefix only when running as the GitHub.com cloud agent; see "Running as the GitHub cloud agent" below. If you are executing git commands on the user's local machine, you are not the cloud agent — usebackport/. <id>is the PR number (PR source), the source branch's last segment, or<short-sha>(single-commit / range).<target-slug>is the target branch with every/replaced by-. Compute it, don't hand-write it (e.g. in shell:target_slug="${target//\//-}"). Examples:rel/0.32→rel-0.32,rel/0.34→rel-0.34.- Full example: PR #3036 onto
rel/0.34gives branchbackport/3036-to-rel-0.34(correct), notbackport/3036-to-rel/0.34(wrong — the/from the target was left in). - Verify before checkout: the final branch name must contain exactly one
/(the separator right afterbackport). If it contains two, the slug wasn't applied — recompute it before creating the branch.
- Prefix is
- Collision handling (local or
origin/<branch>exists): ask the user — overwrite (delete local + remote, recreate) or use a numeric suffix (-2,-3, …). Never silently overwrite. git checkout -b <name> origin/<target>.
Phase D — Cherry-pick
- Squash:
git merge --squash <source>thengit commit -m "Backport #<n>: <original-title>"(or a synthesized title for non-PR sources). - Otherwise:
git cherry-pick <sha…>in order.
Conflict policy (relaxed but safe):
Cloud-agent override: when running inside the GitHub Copilot cloud agent (see "Running as the GitHub cloud agent" below), use the stricter policy: auto-resolve only trivial cases per step 2; for anything ambiguous, do not invent a resolution — commit the conflict markers as a
WIP:commit and mark the PR as draft. Skip steps 3 and 4 (no interactive prompts, no squash-and-retry).
- On conflict, run
git statusandgit diffto inspect. - Auto-resolve only trivial cases:
- Import-order: only the order of
import/requirestatements differs; identifiers are identical. - Formatting-only: whitespace, trailing comma, or semicolon changes where no identifiers, literals, or control flow differ between sides.
- Additive non-overlapping hunks: one side adds lines while the other side is unchanged in that region.
- Pure deletions on one side: one side deletes a block, the other leaves it untouched, and the deleted block is not referenced by code added on either side.
After resolving, verify with
git diff --checkand! grep -R '<<<<<<<' -- .before staging.
- Import-order: only the order of
- For anything ambiguous (semantic overlap, both branches modified the same hunk meaningfully, version/lockfile bumps, generated files): stop and present the conflicting files and hunks to the user. Offer:
- Resolve manually & continue — wait for the user, then
git add+git cherry-pick --continue. - Abort —
git cherry-pick --abort, delete the backport branch, restore (Phase G). - Squash and retry — only when (a) more than one commit, (b) the current run was not already squash, (c) the conflicting commit is not the last. Abort current cherry-pick, delete the branch, restart from Phase C with squash enabled.
- Resolve manually & continue — wait for the user, then
- Record every conflict and its resolution for the PR body.
Phase E — Local validation (recommended)
Cherry-picks onto older release branches often produce code that compiles on the source's base but breaks on the target (different deps, removed APIs, stricter lint config). Catch this before pushing:
- Ask the user whether to run validation. Default: yes. Offer to skip for speed.
- If yes, run the repo's standard checks following .github/copilot-instructions.md. Because the working tree was switched from the original branch to
origin/<target>and may have been mutated by the cherry-pick,node_modulesis almost certainly stale — always start withnpm install. Then run, in this order:npm run build,npm run l10n,npm run prettier-fix,npm run lint. Always runnpm run l10nregardless of whether the cherry-pick obviously touches user-facing strings: dependency bumps can alter embedded error messages, and the target release branch may carry pre-existing l10n drift that the CIl10n:checkwill fail on. If the bundle changes, commit the regenerated bundle as a separatechore: regenerate l10n bundlecommit on the backport branch before the Phase F push. - Pull translations from the source's base branch — only when the user opted into validation in step 1, the cherry-pick changed
l10n/bundle.l10n.json(English bundle), and the source PR is merged. After the original PR merged, a localization bot typically commits translated strings to the source's base (e.g.main) — those translations apply equally to the backport and should not be re-translated by hand. Pull only the affected keys, never wholesale-replace language files: the source base may have other strings the target branch must not gain. Procedure:- Determine the set of changed keys in the English bundle on the backport branch versus the target. Compare keys (top-level JSON object keys) between
git show origin/<target>:l10n/bundle.l10n.jsonand the currentl10n/bundle.l10n.json. Record:- Added keys — present in current, absent in target.
- Modified keys — present in both but with different values (rare, but possible if the English text changed).
- Removed keys are irrelevant for translation pulling (already gone from the English bundle, will be dropped from language files by
npm run l10n).
- Do the same comparison for
package.nls.json(its translations live inpackage.nls.<lang>.json). - For each language file (
l10n/bundle.l10n.<lang>.jsonandpackage.nls.<lang>.json, every<lang>present in the repo), read the source-base version withgit show origin/<source-base>:<file>, then for each added/modified key copy that key's translated value into the local language file. Leave all other keys in the local file untouched. Preserve the existing line endings and key order of the local file: translation pipelines often write CRLF and use a non-obvious collation order, and re-sorting or re-serializing produces a massive cosmetic diff that reviewers will reject. Insert each new key adjacent to the nearest preceding key (in source-base order) that already exists locally; serialize withJSON.stringify(obj, null, 2), then convert\nback to\r\nif the local file used CRLF. If the source-base version is missing a key (translation bot hasn't run yet), skip it —npm run l10nwill leave the English fallback. - Re-run
npm run l10nto refresh the English bundle (the language files are not modified by this script in current builds; it only normalizesl10n/bundle.l10n.json). - Stage only the language files that actually changed (
git add l10n/bundle.l10n.*.json package.nls.*.json) and commit aschore: pull updated translations from <source-base>. Do not stagel10n/bundle.l10n.jsonorpackage.nls.jsonhere — those belong to the earlierchore: regenerate l10n bundlecommit. - If the source PR is not merged (open or closed without merging), skip this step — translations don't exist yet.
- Determine the set of changed keys in the English bundle on the backport branch versus the target. Compare keys (top-level JSON object keys) between
- On failure: surface the errors and stop. Treat fixes as a new round of conflict resolution — only modify what's needed; never silently pile on unrelated changes. Once green, continue.
- If the user opts to skip, note this in the PR body so reviewers know CI is the first validation gate.
Phase F — Push & open the PR
git push -u origin <branch>— never--forceor--force-with-lease. If the push fails, surface the fullgiterror. If it is a permission or branch-protection error (e.g. protected-branch hook, missing write access), advise the user to check repository settings; do not retry with force flags. Proceed to Phase G failure cleanup.- Write the PR body to a file first, then pass it with
--body-file. Prefer the editor's file-creation tool (cross-platform); if you use a shell heredoc, write to the OS temp directory outside the repo so it can't be accidentally staged or committed — e.g."${TMPDIR:-/tmp}/backport-body.md"on macOS/Linux or"$env:TEMP\backport-body.md"on Windows PowerShell — and delete it once the PR is created. Then rungh pr create --base <target> --head <branch> --title "[<target>] <original-title>" --body-file <path>. Always use--body-file; never--body "…". An inline multi-line body gets mangled — the shell eats backticks/$/quotes and any literal\nsequences are printed verbatim in the PR description. Authoring rules for the body file:- Real newlines only. Type actual line breaks; never write the two characters
\nto mean a newline. - Do not hard-wrap sentences. Keep each paragraph or bullet on a single line and separate blocks with one blank line. In PR descriptions and comments (unlike rendered
.mdfiles in a repo) GitHub honors a single newline as a hard line break, so column-wrapped prose shows up broken mid-sentence; hard-wrapping also makes the body diff noisily and read awkwardly in plaintext previews. If you reuse the original PR description, re-flow it first — strip any existing column wrapping so each paragraph is one continuous line. - No task-list checkboxes (
- [ ]/- [x]). GitHub turns them into a "N tasks done / Progress 100%" bar on the PR list, and pre-checked boxes falsely imply a reviewer checklist is already complete. Use plain-bullets for the validation summary and everything else. - Title: must start with the target branch in square brackets, e.g.
[rel/0.34] Fix tree refresh race. Use the exact target branch name (including anyrel/prefix) and keep the rest identical to the original PR title (or first commit subject for non-PR sources). - Contents:
Backport of #<n>(orBackport of <branch>/ SHA list for non-PR sources); the original PR description (when applicable); a Conflicts resolved section listing each file + a one-line description, when applicable.
- Real newlines only. Type actual line breaks; never write the two characters
gh pr view --webto open it in the browser.
Phase G — Cleanup
- On success:
git checkout <originalBranch>. If a stash was created in A.3,git stash pop <stashRef>. Print the new PR URL. - On failure or user-requested abort: leave the backport branch and stash intact. Print the stash ref (if any) and the recovery commands (
git checkout <originalBranch> && git stash pop <stashRef>).
Constraints
- Refuse the source's own base branch as the target (a backport onto the same base is a no-op). Any other existing branch on
originis allowed.mainis allowed only when it is not the source's base; if the source already targetsmain, refusemainper the rule above. - Never use
--force/--force-with-lease. - Preserve original commit messages during cherry-pick.
- Do not modify files outside what is required for conflict resolution or to fix validation failures introduced by the cherry-pick.
- Never silently overwrite an existing local or remote branch.
- Never auto-resolve ambiguous conflicts; always ask.
- Never restore the autostash on failure — leave it for the user to inspect.
- Branch prefix: use
backport/for local runs. Use thecopilot/prefix only when running as the GitHub.com cloud agent (it cannot push other prefixes). Never usecopilot/for a local run. - Branch slug: replace every
/in the target with-when building the branch name; the final name has exactly one/(right afterbackport/copilot). Compute the slug, don't hand-copy the target branch. - PR body: always pass it via
--body-file, never inline--body. No literal\n, no hard-wrapped sentences, no task-list checkboxes (- [ ]/- [x]).
Reporting
When done, summarize:
- Source, target, backport branch name, commits cherry-picked.
- Any conflicts encountered and how they were resolved.
- Whether local validation ran and its result (or that the user opted to skip).
- The new PR URL.
- Whether the original branch and stash were restored.
npx skills add https://github.com/microsoft/vscode-cosmosdb --skill backportRun this in your project — your agent picks the skill up automatically.
Running as the GitHub cloud agent
When this skill runs inside the GitHub Copilot cloud agent (e.g. invoked by @copilot on a merged PR or assigned to a backport issue), the environment differs from a local VS Code workspace. Adjust the workflow as follows:
- No interactive prompts. You cannot pause to ask the user. Resolve everything from the request body and repository state up front; if a required input is missing or ambiguous (target branch, source PR), stop and report rather than guess.
- Do not abort on
gh auth statusfailure (Phase A step 1 override). In the cloud agent, git push auth comes from the platform's credential helper and the PR is opened by the platform — neither depends ongh. A failinggh auth status(e.g.GITHUB_TOKENreported invalid) must not stop the backport. Proceed with all git work (fetch, branch, cherry-pick, push); if the token really is unusable,git pushwill fail later with its own clear error — that is strictly better than abandoning a completed cherry-pick at pre-flight. Only PR-metadata commands (gh pr edit) needgh: if it is unauthenticated, first retry once asGH_TOKEN="$GITHUB_TOKEN" gh pr edit …; if that still fails, set the base/title/body via the REST API (gh api -X PATCH repos/{owner}/{repo}/pulls/{number} -f base=<target> -f title="[<target>] …", orcurlwith the token), and if even that is impossible, leave the PR open and state explicitly in the body which fields — especially the base branch — still need to be set manually so it does not silently targetmain. - Branch naming: use
copilot/backport-<id>-to-<target-slug>instead ofbackport/.... Thiscopilot/prefix applies only here, in the GitHub.com cloud agent, because the cloud agent can only push branches starting withcopilot/; a local run must use thebackport/prefix from Phase C. The<target-slug>rule is unchanged — replace every/with-, sorel/0.34yieldscopilot/backport-<id>-to-rel-0.34(one/only, right aftercopilot). - Skip Phase A.3 stash logic — the cloud agent runs in a fresh ephemeral checkout; there is no user working tree to preserve.
- Apply the Phase D cloud-agent override described above (commit conflict markers as
WIP:and mark the PR draft instead of asking, aborting, or squash-and-retry). - Skip Phase E (local validation) — the cloud agent's GitHub Actions environment runs CI as the validation gate; do not run
npm install/ build / lint to save time and avoid burning Actions minutes. - Skip Phase F's
gh pr create. The cloud agent platform opens the PR for the task automatically. Write the body to a file and usegh pr edit --base <target> --title "[<target>] <original-title>" --body-file <path>to set the base branch, title, and body — never inline--body "…", and follow the same Phase F body-authoring rules (real newlines, no literal\n, no hard-wrapped sentences, no task-list checkboxes). The title must be prefixed with the target branch in square brackets — e.g.[rel/0.34] <original-title>. When conflicts remain unresolved, mark the PR as draft (the platform may open it ready by default; usegh pr ready --undoif available, otherwise note the WIP state explicitly in the PR body). - Skip Phase G's stash restore and original-branch checkout — there's nothing local to restore.
All other constraints (no --force, refuse the source's own base, preserve commit messages, never silently overwrite existing branches) apply unchanged.
No common issues documented yet. If you hit a problem, the repository's GitHub Issues page is the best place to look.
Licensed under MIT— you can use, modify, and redistribute it under that license's terms.
View the full license file on GitHub →