From 711ec2f23b5e606c5f9eae9af72b81b3b7e445bd Mon Sep 17 00:00:00 2001 From: garrytan Date: Sun, 25 Oct 2026 23:31:59 +0000 Subject: [PATCH] ci(weekly-release): a PR touching the release tooling runs the dry run against main (read-only); the cut job checks out the dispatched ref Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- .github/workflows/weekly-release.yml | 54 +++++++++++++++++++++++----- CHANGELOG.md | 2 +- docs/releases.md | 5 +++ 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/.github/workflows/weekly-release.yml b/.github/workflows/weekly-release.yml index d4db738ce..ac5b546bd 100644 --- a/.github/workflows/weekly-release.yml +++ b/.github/workflows/weekly-release.yml @@ -3,9 +3,12 @@ name: Weekly release # Release, and fast-forwards refs/heads/stable to it (docs/releases.md). Team # installs follow stable, so this is the only thing that moves them. Runs # every Monday morning Pacific; run it by hand with a ref for a hotfix, or -# with dry_run to see the plan and change nothing. The candidate's gate must -# already be green (free-tests, no red check run, a green Main backstop gate -# run, no active freeze): this workflow never runs tests or paid evals. +# with dry_run to see the plan and change nothing. A pull request that touches +# the release tooling runs the dry run against main with a read-only token, +# which is the only pre-merge exercise of the real workflow. The candidate's +# gate must already be green (free-tests, no red check run, a green Main +# backstop gate run, no active freeze): this workflow never runs tests or +# paid evals. on: schedule: # Monday 14:00 UTC: 07:00 PDT, 06:00 PST. @@ -20,10 +23,15 @@ on: description: 'Hotfix: release this commit of main instead of the newest green one (full SHA)' type: string default: '' + pull_request: + paths: + - '.github/workflows/weekly-release.yml' + - 'scripts/release-cut.ts' -# One cut at a time; a second dispatch waits for the first. +# One cut at a time; a second dispatch waits for the first. A pull request's +# dry run is its own group. concurrency: - group: weekly-release + group: weekly-release-${{ github.event.pull_request.number || 'cut' }} cancel-in-progress: false permissions: {} @@ -31,6 +39,7 @@ permissions: {} jobs: cut: name: Cut the release + if: github.event_name != 'pull_request' runs-on: ubuntu-24.04 timeout-minutes: 20 permissions: @@ -40,9 +49,9 @@ jobs: actions: read checks: read steps: - # The dispatched ref supplies the tooling (a dry run from a PR branch - # exercises that branch's script); the release candidate always comes - # from origin/main, which fetch-depth 0 brings along with every branch. + # The dispatched ref supplies the tooling; the release candidate always + # comes from origin/main, which fetch-depth 0 brings along with every + # branch. - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: 0 @@ -71,3 +80,32 @@ jobs: [ -n "$RELEASE_REF" ] && args+=(--ref "$RELEASE_REF") echo "## Weekly release ($([ "$DRY_RUN" = true ] && echo 'dry run' || echo 'cut'))" >> "$GITHUB_STEP_SUMMARY" bun --no-env-file --config=/dev/null --no-install run scripts/release-cut.ts "${args[@]}" + + dry-run: + name: Dry run against main + if: github.event_name == 'pull_request' + runs-on: ubuntu-24.04 + timeout-minutes: 20 + permissions: + contents: read + actions: read + checks: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.4.2 + + - name: Plan only + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + git fetch --quiet origin '+refs/heads/stable:refs/remotes/origin/stable' 2>/dev/null || echo "origin has no stable yet" + git fetch --quiet --tags origin + echo "## Weekly release (dry run from PR #${{ github.event.pull_request.number }})" >> "$GITHUB_STEP_SUMMARY" + bun --no-env-file --config=/dev/null --no-install run scripts/release-cut.ts --repo "$GITHUB_REPOSITORY" --summary "$GITHUB_STEP_SUMMARY" --dry-run diff --git a/CHANGELOG.md b/CHANGELOG.md index 390d7fade..571b62493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ **Weekly tagged releases, and a `stable` channel that team-mode auto-update follows by default.** Main kept stamping a version per merge, a dozen a day, with no tag or release anywhere, and `./setup --team` installs moved to every one of them within the hour. Now a Monday-morning workflow tags the newest green commit on main as `v`, publishes a GitHub Release from the CHANGELOG headlines and fast-forwards a `stable` branch to it; team installs follow `stable` and move once a week (plus hotfixes), contributors keep following main, and one config key switches either way. Nothing about per-merge stamping or the merge train changed (docs/releases.md). ### Added -- **`.github/workflows/weekly-release.yml`**, **`scripts/release-cut.ts`**: every Monday at 14:00 UTC, or by `workflow_dispatch` (`dry_run`, default true for a manual run; `ref` for a hotfix), the newest first-parent commit on main whose `free-tests` check run succeeded, with no red check run, a Main backstop gate run that concluded success and no active freeze in `refs/notes/gstack-trains`, is tagged `v`, released with one headline per CHANGELOG version since the previous release (capped at 60 KB), and becomes `refs/heads/stable`. Stable only moves forward along main; a candidate behind it, a tag naming another commit, or a frozen queue is `REFUSED`. A dry run prints the plan and the exact commands and changes nothing. The job has `contents: write` only; both rulesets cover `refs/heads/main` alone, so the token can push the tag and `stable`. +- **`.github/workflows/weekly-release.yml`**, **`scripts/release-cut.ts`**: every Monday at 14:00 UTC, or by `workflow_dispatch` (`dry_run`, default true for a manual run; `ref` for a hotfix), the newest first-parent commit on main whose `free-tests` check run succeeded, with no red check run, a Main backstop gate run that concluded success and no active freeze in `refs/notes/gstack-trains`, is tagged `v`, released with one headline per CHANGELOG version since the previous release (capped at 60 KB), and becomes `refs/heads/stable`. Stable only moves forward along main; a candidate behind it, a tag naming another commit, or a frozen queue is `REFUSED`. A dry run prints the plan and the exact commands and changes nothing; a pull request that touches the workflow or the script runs that dry run against main as a read-only job. The cut job has `contents: write` only; both rulesets cover `refs/heads/main` alone, so the token can push the tag and `stable`. - **`bin/gstack-update-channel.sh`**: resolves the channel (`gstack_update_channel`: `update_channel` `auto` is `stable` when `team_mode` is true, else `main`; `GSTACK_UPDATE_CHANNEL` overrides), and as a command (`--fetch-ref`) fetches the channel ref and prints `origin/stable` or `origin/main` for the /gstack-upgrade skill. - **`bin/gstack-config`**: new key `update_channel` (`auto` | `stable` | `main`), validated on `set`, in the header, `list` and `defaults`. - **`docs/releases.md`**: what a release is, what green means, the channels and how to switch, hotfixes, dry runs and every outcome line. diff --git a/docs/releases.md b/docs/releases.md index 19fde4514..9ebc29f7a 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -113,6 +113,11 @@ gh run watch The step summary carries the same plan plus the full release notes. +A pull request that touches `.github/workflows/weekly-release.yml` or +`scripts/release-cut.ts` runs the same dry run against main as its own job +(`Dry run against main`, read-only token), so a change to the release tooling +is exercised against real check runs before it merges. + ## Outcomes | Line | Meaning |