--- name: ship-release description: Drive a Telepresence release from a prepared branch all the way through CI, docs, the Releases workflow, and PR merges. Assumes `make prepare-release` has already been run locally and the branch with that commit was pushed and a PR opened. Use when the user says "ship the release", or "complete the release". User-only. disable-model-invocation: true --- # ship-release End-to-end driver for releasing Telepresence. Picks up where `prepare-release` left off and carries the change through: 1. Telepresence release PR (CI green + `regression` green) 2. Docs PR in `../telepresence.io` 3. Tag push, Releases workflow, merge of both PRs. This is **user-only** (`disable-model-invocation: true`). A release is publicly visible and partially irreversible (tags push to GitHub, Homebrew updates for GA). Claude must never invoke this on its own. ## Preconditions to verify before doing anything Run each check and stop with a clear message if any fails: 1. **CWD is the telepresence repo.** `git rev-parse --show-toplevel` ends in `telepresence`. 2. **`make prepare-release` has been run.** The current HEAD must carry both `vX.Y.Z` and `rpc/vX.Y.Z` annotated tags locally: ``` git tag --points-at HEAD | sort ``` Two entries expected. If the tag list is empty or missing the `rpc/` peer, stop — the user needs to run `make prepare-release` first. 3. **Branch is pushed.** Capture `tp_branch=$(git branch --show-current)`, then: ``` git ls-remote --exit-code origin "refs/heads/$tp_branch" ``` If this fails, stop and tell the user to push the branch first. 4. **PR exists.** `gh pr view "$tp_branch" --json number,state,url,headRefName`. If no PR, stop. 5. **Sibling docs repo present.** `test -d ../telepresence.io && test -d ../telepresence.io/.git`. If not, stop. Capture once and reuse throughout: - `tp_branch` — name of the prepare-release branch. - `tp_version` — pick the non-`rpc/` tag from `git tag --points-at HEAD` (e.g. `v2.28.0`). - `docs_version` — `echo "$tp_version" | sed -E 's/^v([0-9]+\.[0-9]+).*/\1/'` (e.g. `2.28`). - `pr_number` — from `gh pr view`. ## Phase 1 — Drive the Telepresence release PR ### 1.1 Verify branch and PR (already done in preconditions) ### 1.2 Wait for all checks except `regression` to be green Use: ``` gh pr checks "$tp_branch" --json name,state,conclusion ``` Filter out the rows whose name is exactly `regression` or `node_agent_docker_runtime` (neither has been triggered yet — the label triggers them). Match the name exactly: `regression_compat` is a different job, and it only runs behind the `compatibility test` label. For every remaining row: - `state == "COMPLETED"` and `conclusion == "SUCCESS"` → green - `conclusion ∈ {"FAILURE","CANCELLED","TIMED_OUT","ACTION_REQUIRED"}` → **stop**. Report the failing check name and a short excerpt from `gh run view --log-failed`. Do not advance. - Anything else (`IN_PROGRESS`, `QUEUED`, `PENDING`) → keep waiting. **Polling cadence:** these checks (lint, unit tests, license, image-scan) typically finish in 5-15 min. Use `ScheduleWakeup` with `delaySeconds=180` while any check is still running. Do not tight-loop with sleeps. ### 1.3 Wait for `regression` to be green `regression` starts automatically with the push (the release branch lives in this repository); there is nothing to trigger. If it needs another attempt, use "Re-run all jobs" on its workflow run (`gh run rerun `). The regression suite runs as three parallel shards (~30 min including cluster setup), summed into the single `regression` context; the node-agent job runs beside them. Use `ScheduleWakeup` with `delaySeconds` around **900**. Poll with the same `gh pr checks` query, looking at the `regression` and `node_agent_docker_runtime` rows. - Success → continue to Phase 2. - Failure / cancellation → **stop and report**. Pull failed-step logs with `gh run view --log-failed`. - Still running after ~90 minutes → tell the user and stop (workflow may be stuck). ## Phase 2 — Create the docs PR Done in the sibling repo `../telepresence.io`. Each shell step below is a separate Bash call (no `&&` chains), and `cd` to switch repos. ### 2.1 Pull master ``` cd ../telepresence.io git checkout master git pull ``` ### 2.2 Create branch with the same name as the telepresence PR branch ``` git checkout -b "$tp_branch" ``` If that branch already exists locally from a previous attempt, stop and ask whether to reuse, reset, or rename. ### 2.3 + 2.4 Export variables ``` export DOCS_VERSION="$docs_version" # e.g. 2.28 — note: no patch number export DOCS_BRANCH="$tp_branch" ``` (Per CLAUDE.md, `export` in its own Bash call, then use in subsequent calls. Shell state persists between calls in a session.) ### 2.5 Generate ``` make generate-version ``` ### 2.6 Verify output ``` ls versioned_docs/version-"$DOCS_VERSION" git status ``` Expectations: - **Minor release** (first time this `2.X` is generated): the directory `versioned_docs/version-$DOCS_VERSION/` appears as untracked. - **Bugfix release** (directory already existed): files within it are modified. If `versioned_docs/version-$DOCS_VERSION` is absent or `git status` shows no changes, **stop and report** — `make generate-version` did not do anything useful. ### 2.7 Build the site locally before pushing Netlify (the `deploy/netlify`, `Pages changed`, `Header rules`, `Redirect rules` checks) and the `Check`/`Lint` GitHub jobs all run `yarn build` (docusaurus build). Run it locally first so a broken build is caught here, not after a push-and-wait CI cycle: ``` # If node_modules is absent: yarn install --frozen-lockfile yarn build ``` - Exit 0 → the production build (including the new `version-$DOCS_VERSION`) compiled. Proceed to the PR. - Non-zero → **stop and do not push.** Read the error; it names the offending file and line. **Most common failure: MDX parse error in `release-notes.mdx`.** A `.mdx` file is JSX, so literal `{` / `}` **inside an HTML element** like `{cmd, stdout}` are parsed as JS expressions and fail with `Could not parse expression with acorn`. (Braces inside Markdown backtick spans — `` `{tcp|udp}` `` — are safe.) These release-notes files are generated, so fix the **source**, not the generated copy: 1. In the telepresence repo, edit the offending `CHANGELOG.yml` entry to remove the literal braces (rephrase, e.g. `cmd/stdout`, or move the snippet into a backtick span), then `make docs-files`. 2. Commit + push that fix to the release branch (it belongs in the release PR). 3. Back in the docs repo, re-run `make generate-version` to re-pull the fixed docs, then `yarn build` again before continuing. ### 2.8 Create the PR ``` git add versioned_docs/version-"$DOCS_VERSION" versioned_sidebars docusaurus.config.js versions.json # (Add only the files that actually changed — git status will tell you which of the # above moved; for a fresh minor you'll likely see all of them, for a bugfix only some.) git commit -s -S -m "Generate docs for telepresence $tp_version" git push -u origin "$tp_branch" gh pr create --base master --head "$tp_branch" \ --title "Generate docs for telepresence $tp_version" \ --body "Generated with \`make generate-version\` DOCS_VERSION=$docs_version DOCS_BRANCH=$tp_branch." ``` Capture `docs_pr_number` from the `gh pr create` output URL. Do not include "Co-Authored-By" or "Generated with" trailers in the commit message or the PR body (per global preferences). ### 2.9 Monitor the docs PR checks ``` gh pr checks "$tp_branch" --json name,state,conclusion ``` Same polling rules as Phase 1.2. If anything fails, **stop and report**. ## Phase 3 — Release `cd` back to the telepresence repo for step 3.1 and 3.3a. ### 3.1 Push the release tags ``` git push origin "$tp_version" "rpc/$tp_version" ``` This triggers the **Releases** workflow (`.github/workflows/release.yaml`). The release PR is still unmerged at this point — that is intentional. Merging now would create a new commit and move the branch tip away from the tagged commit. ### 3.2 Monitor the Releases workflow ``` gh run list --workflow=release.yaml --limit 1 --json databaseId,status,conclusion,url gh run view --json jobs ``` The workflow requires manual approval of a protected GitHub environment (`macos-signing`) containing secrets for macOS signing. Wait for this up to **24 hours**. Poll with `ScheduleWakeup` at `delaySeconds=1800` (or longer when overnight). Surface the workflow URL early so the user can chase the approver. - If the workflow completes successfully → continue to 3.3. - If a job other than `build-macos-pkg` fails → **stop and report**. - If `build-macos-pkg` itself is never approved within 24h → tell the user; per CLAUDE.md the release still ships without `.pkg` installers, and the user can decide whether to proceed to 3.3 anyway. ### 3.3 Merge both PRs — **GA versions only** **For pre-release versions (`-test.N`, `-rc.N`): skip this step entirely and stop here.** Both PRs stay open until the GA release ships: the release branch accumulates the rc and GA prepare-release commits and merges once, after GA, and the docs PR must not publish the new version's docs on telepresence.io before GA exists (regenerate it from the GA branch before merging). The rc's GitHub pre-release and its tags are the only public artifacts of a pre-release ship. For a GA version: order does not matter. Both must use a **merge commit** (CLAUDE.md: never squash, never rebase). ``` # telepresence PR (in telepresence repo) gh pr merge "$tp_branch" --merge # docs PR (in ../telepresence.io) cd ../telepresence.io gh pr merge "$tp_branch" --merge ``` Verify each merged: `gh pr view "$tp_branch" --json state` should report `MERGED`. ## Long-wait strategy - Anything under 5 min → don't sleep; just poll once. - 5-30 min waits (Phase 1.2 non-regression checks) → `ScheduleWakeup` with `delaySeconds=180`. - 30-60 min waits (Phase 1.3 `regression`) → `ScheduleWakeup` with `delaySeconds=1200`. - Hours-to-overnight (Phase 3.2 macOS signing approval) → `ScheduleWakeup` with `delaySeconds=1800` or longer. Each wake-up: re-fetch state, decide green/red/still-waiting, schedule the next wake or advance. ## What "stop and report" means - Do not advance to the next numbered step. - Surface: the step that failed, the check/run name(s), the run URL(s), and a short excerpt from `gh run view --log-failed`. - Do not retry automatically. Wait for the user to direct. - Do not delete branches, force-push, or close PRs. The user decides what to do. ## What this skill must NEVER do - Run `make prepare-release` itself — that's a separate skill and a separate decision. - Push tags before all required PR checks are green (Phase 1 must complete first). - Merge the release PR or the docs PR for a pre-release (`-test.*`/`-rc.*`) version — both stay open until GA (see 3.3). - Merge PRs as squash or rebase — both repos require merge commits. - Trigger `regression` by any means other than the push itself or a re-run of its workflow run. - Approve the `macos-signing` environment programmatically — that requires a human reviewer. - Force-push or delete the release branch.