| name | cherrypick-to-release |
| description | Resolves a commit and cherry-picks it into one or more client release branches. By default, cherry-picks into BOTH preview and stable. Use when the user asks to cherrypick, backport, or apply a hotfix to a release branch. Optionally waits for a public commit to sync into an internal repo before creating release PRs assigned to the current on-call. |
cherrypick-to-release
Cherry-pick a commit into release branches and open PRs assigned to the current on-call.
By default, cherry-pick into every channel in $RELEASE_CHANNELS (e.g. preview and stable). Only cherry-pick into a single channel if the user explicitly names one in their prompt.
Configuration
This skill reads environment-specific values from environment variables (see the repo README.md):
INTERNAL_REPO — owner/repo that holds your release branches (release cherry-picks always land here).
PUBLIC_REPO — optional owner/repo of a public repo that syncs into INTERNAL_REPO. Only relevant if your workflow maps public commits to internal ones.
REPO_DIR — absolute path to your local checkout of INTERNAL_REPO.
GITHUB_ORG — GitHub org used to resolve the on-call's GitHub username.
SLACK_BOT_TOKEN — Slack bot token (used to look up the on-call).
ONCALL_SLACK_GROUP — Slack usergroup handle for the on-call (e.g. oncall-primary).
RELEASE_CHANNELS — space-separated, ordered release channels (default preview stable). Cherry-picks run in this order.
RELEASE_BRANCH_PREFIX — per-channel branch path prefix; {channel} is replaced with the channel name (default {channel}_release/).
DEFAULT_BRANCH — internal default branch searched for the synced commit (default main).
CHERRYPICK_BRANCH_PREFIX — prefix for created cherry-pick branches (default cherrypick/).
SYNC_TRAILER_KEY — commit trailer key identifying a synced commit's public origin (default Repo-Sync-Origin).
All release-branch git operations run inside INTERNAL_REPO. Run cd "$REPO_DIR" before fetching internal history or creating cherry-picks. If REPO_DIR is not set, ask the user for the local path to their release repo.
Step 1: Clarify inputs and identify the source repo
Before proceeding, ensure you have:
- Channels: Default to all channels in
$RELEASE_CHANNELS. Only use a single channel if the user explicitly requested it.
- Source repo: Determine whether the commit comes from
INTERNAL_REPO or (if configured) PUBLIC_REPO.
- Set
SOURCE_REPO to the identified owner/repo value for the commands below.
- An
INTERNAL_REPO commit is already the synced commit the user wants to cherry-pick. Use it directly.
- A
PUBLIC_REPO commit or PR must be mapped to its synced INTERNAL_REPO commit in Step 2.
- For a bare SHA, check whether it exists in
INTERNAL_REPO first, then PUBLIC_REPO. If neither repo contains it, ask the user for the source repo or URL.
- For a bare PR number, ask which repo it belongs to.
- Source commit hash: Resolve the full SHA in the source repo. If the user gave a PR URL, get its merge commit SHA:
gh pr view <PR_NUMBER> --repo "$SOURCE_REPO" --json mergeCommit --jq '.mergeCommit.oid'
If the PR is not merged, stop and tell the user.
- Original PR title: If the user provided a PR URL or number, fetch the title for use in the cherry-pick PR title:
gh pr view <PR_NUMBER> --repo "$SOURCE_REPO" --json title --jq '.title'
If the user only provided a commit hash, use the first line of the source commit message:
gh api "repos/$SOURCE_REPO/commits/<SOURCE_COMMIT_HASH>" --jq '.commit.message | split("\n")[0]'
- Release branch(es): Check whether the user specified a branch name or version string (e.g.
stable_release/v0.YYYY.MM.DD.HH.MM.stable_NN or just v0.YYYY.MM.DD). If so, use it directly and skip Step 3.
Step 2: Resolve the internal cherry-pick commit
The final COMMIT_HASH must identify a commit in INTERNAL_REPO. Never cherry-pick a public PUBLIC_REPO SHA directly into an internal release branch.
If the source is INTERNAL_REPO
Set COMMIT_HASH to the full INTERNAL_REPO source commit SHA and continue to Step 3. Do not search for a corresponding public commit.
If the source is PUBLIC_REPO (optional public↔internal sync)
If your workflow syncs public commits into INTERNAL_REPO, the sync typically rewrites commit SHAs and adds a trailer keyed by $SYNC_TRAILER_KEY (default Repo-Sync-Origin) to the synced internal commit identifying its origin, for example:
Repo-Sync-Origin: <PUBLIC_REPO>@<PUBLIC_SHA>
From "$REPO_DIR", fetch and search the internal default branch history for the synced commit. If it is not present yet, poll once per minute for up to one hour:
PUBLIC_SHA=<full-public-source-commit-sha>
COMMIT_HASH=""
for attempt in {1..60}; do
git fetch origin "$DEFAULT_BRANCH"
COMMIT_HASH=$(git log FETCH_HEAD --format='%H' \
--grep="${SYNC_TRAILER_KEY}: ${PUBLIC_REPO}@$PUBLIC_SHA" -1)
[[ -n "$COMMIT_HASH" ]] && break
sleep 60
done
If COMMIT_HASH is still empty after the final attempt, stop and tell the user that the commit has not synced to INTERNAL_REPO. Do not create the cherry-picks until the synced internal commit is available.
Step 3: Find the latest release branches
Skip this step if the user already provided a specific branch name or version.
git fetch
CHANNEL=stable
PREFIX=$(printf '%s' "$RELEASE_BRANCH_PREFIX" | sed "s|{channel}|$CHANNEL|")
git branch -r | grep -oE "${PREFIX}.*" | sort | tail -n 1
Run this for each target channel in $RELEASE_CHANNELS. If the user gave a version string but not a full branch name, use the grep output to find the matching branch. Show the results to the user and confirm before proceeding.
Step 4: Run the cherry-picks
Repeat the following for each target channel, in the order listed in $RELEASE_CHANNELS.
BRANCH_NAME=<release-branch-from-step-3>
CP_BRANCH="${CHERRYPICK_BRANCH_PREFIX}${BRANCH_NAME}_${COMMIT_HASH:0:7}"
git checkout $BRANCH_NAME
git pull
git checkout -b $CP_BRANCH
git cherry-pick $COMMIT_HASH
If there is a conflict, stop and tell the user. They must resolve it manually, run git cherry-pick --continue, then push:
git push --set-upstream origin $CP_BRANCH --no-verify
If no conflict, push immediately:
git push --set-upstream origin $CP_BRANCH --no-verify
After completing the first channel, proceed to the second channel.
Step 5: Find the current on-call
Look up the current on-call automatically using the Slack API. Requires $SLACK_BOT_TOKEN to be set in the environment. The token needs these OAuth scopes: usergroups:read, users:read, users:read.email.
GROUP_ID=$(curl -s -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
"https://slack.com/api/usergroups.list" | \
jq -r --arg handle "$ONCALL_SLACK_GROUP" '.usergroups[] | select(.handle==$handle) | .id')
USER_ID=$(curl -s -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
"https://slack.com/api/usergroups.users.list?usergroup=$GROUP_ID" | \
jq -r '.users[0]')
curl -s -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
"https://slack.com/api/users.info?user=$USER_ID" | \
jq '.user.profile | {name: .real_name, email: .email}'
If $SLACK_BOT_TOKEN is not set, ask the user: "Who is the current on-call (@$ONCALL_SLACK_GROUP in Slack)?"
Once you have their name, find their GitHub username with:
gh api "/orgs/$GITHUB_ORG/members" --paginate --jq '.[] | select(.login | ascii_downcase | contains("<firstname>")) | .login'
Confirm with the user if the match is ambiguous.
Step 6: Open the PRs
Create a PR for each channel's cherry-pick branch. For each:
Determine the channel label from the channel name — capitalize it and append CP (e.g. preview → Preview CP, stable → Stable CP):
CP_LABEL="$(printf '%s' "${CHANNEL:0:1}" | tr '[:lower:]' '[:upper:]')${CHANNEL:1} CP"
gh pr create \
--repo "$INTERNAL_REPO" \
--base $BRANCH_NAME \
--head $CP_BRANCH \
--title "[$CP_LABEL] <original PR title>" \
--body-file .github/PULL_REQUEST_TEMPLATE/cherrypick.md \
--reviewer <oncall-github-username>
For example, if the original PR title is "Fix crash on startup" and the channel is preview, the title should be: [Preview CP] Fix crash on startup.
Then share both PR URLs with the user and notify the on-call.
Step 7: Update release status
After cherry-pick PRs have been created, invoke the post-release-status skill to post or update the release status message in Slack. Pass the release branch from Step 3 and the Slack channel (ask the user if not already known).
Notes
- By default, cherry-pick into every channel in
$RELEASE_CHANNELS. Only target a single channel if the user explicitly requests it.
- An
INTERNAL_REPO commit is authoritative. Use it directly rather than looking for a public origin.
- If you use a public↔internal sync, public commits must finish syncing to
INTERNAL_REPO before release cherry-picks begin.
- After the PRs merge, the on-call must start the release-candidate build on each release branch (see the cut-new-release-candidate skill).