ship-release
End-to-end driver for releasing Telepresence. Picks up where prepare-release left off and carries the change through:
- Telepresence release PR (CI green +
regressiongreen) - Docs PR in
../telepresence.io - 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:
- CWD is the telepresence repo.
git rev-parse --show-toplevelends intelepresence. make prepare-releasehas been run. The current HEAD must carry bothvX.Y.Zandrpc/vX.Y.Zannotated tags locally:
Two entries expected. If the tag list is empty or missing thegit tag --points-at HEAD | sortrpc/peer, stop — the user needs to runmake prepare-releasefirst.- Branch is pushed. Capture
tp_branch=$(git branch --show-current), then:
If this fails, stop and tell the user to push the branch first.git ls-remote --exit-code origin "refs/heads/$tp_branch" - PR exists.
gh pr view "$tp_branch" --json number,state,url,headRefName. If no PR, stop. - 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 fromgit 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— fromgh 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"andconclusion == "SUCCESS"→ greenconclusion ∈ {"FAILURE","CANCELLED","TIMED_OUT","ACTION_REQUIRED"}→ stop. Report the failing check name and a short excerpt fromgh run view <run-id> --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 <run-id>).
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 <run-id> --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.Xis generated): the directoryversioned_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
<code>{cmd, stdout}</code> 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:
- In the telepresence repo, edit the offending
CHANGELOG.ymlentry to remove the literal braces (rephrase, e.g.<code>cmd</code>/<code>stdout</code>, or move the snippet into a backtick span), thenmake docs-files. - Commit + push that fix to the release branch (it belongs in the release PR).
- Back in the docs repo, re-run
make generate-versionto re-pull the fixed docs, thenyarn buildagain 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 <id> --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-pkgfails → stop and report. - If
build-macos-pkgitself is never approved within 24h → tell the user; per CLAUDE.md the release still ships without.pkginstallers, 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) →
ScheduleWakeupwithdelaySeconds=180. - 30-60 min waits (Phase 1.3
regression) →ScheduleWakeupwithdelaySeconds=1200. - Hours-to-overnight (Phase 3.2 macOS signing approval) →
ScheduleWakeupwithdelaySeconds=1800or 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 <id> --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-releaseitself — 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
regressionby any means other than the push itself or a re-run of its workflow run. - Approve the
macos-signingenvironment programmatically — that requires a human reviewer. - Force-push or delete the release branch.