- 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:
```bash
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:
```bash
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:
```bash
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:
```text
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:
```bash
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.**
```bash
git fetch
# For each target channel, resolve its branch prefix from RELEASE_BRANCH_PREFIX
# (replacing {channel}) and pick the newest matching branch. Example for `stable`:
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`.
```bash
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:
```bash
git push --set-upstream origin $CP_BRANCH --no-verify
```
If no conflict, push immediately:
```bash
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`.
```bash
# 1. Get the on-call usergroup ID
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')
# 2. Get the current member(s)
USER_ID=$(curl -s -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
"https://slack.com/api/usergroups.users.list?usergroup=$GROUP_ID" | \
jq -r '.users[0]')
# 3. Resolve to name and email
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:
```bash
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`):
```bash
CP_LABEL="$(printf '%s' "${CHANNEL:0:1}" | tr '[:lower:]' '[:upper:]')${CHANNEL:1} CP"
```
```bash
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).
GitHubで見る