From e0481f57b14379fbc03a663ef3c105787533b15b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 12:24:46 +0000 Subject: [PATCH] Run `gem:changelog` on a runner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparing a release from a Claude Code on the web session is possible except for this one step: `gem:changelog` and `gem:changelog:json` ask GitHub which pull requests the commits came from, they ask through `gh`, and such a session cannot reach `api.github.com` at all. The refusal is keyed on the session, so neither installing `gh` nor rewriting the task against REST gets around it. docs/release.md answers that today by describing how to assemble the same list with the GitHub MCP server instead — which is a second implementation of `changelog_pull_requests`, written in prose, that has to be kept in agreement with the first. It has already drifted twice. A runner has `gh` and `github.token`, as `gem:gh_release` in release-gems.yml already relies on, so the task can just be run where it works and read back from the log, the summary or the artifact. The ref selector picks the branch the changelog is for and the copy of the task that computes it, which are the same choice. Nothing is written: `contents: read` for the history and the tags, and `pull-requests: read` for what the commits came from. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QAgPhpLrU9Z4bENCR9RUub --- .github/workflows/changelog.yml | 121 ++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 .github/workflows/changelog.yml diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 000000000..78a7af8dc --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,121 @@ +name: Changelog + +# Prints the changelog template for a release, which is otherwise `rake gem:changelog` +# on a laptop. +# +# The task asks GitHub which pull requests the commits came from, and it asks through +# `gh`. A Claude Code on the web session cannot reach `api.github.com` at all -- the +# refusal is keyed on the session, so neither installing `gh` nor rewriting the task +# against REST gets around it -- and a runner has both `gh` and `github.token`. So the +# same task runs here, and the release is prepared from a session that could not +# otherwise produce this list. +# +# The ref selector picks the branch the changelog is for *and* the copy of the task +# that computes it, which are the same thing: dispatch it on `aaa-X.Y.x` for a patch +# release, on `master` otherwise. +# +# Nothing is written. The run reads the repository and prints. + +on: + workflow_dispatch: + inputs: + version: + description: "Where the changelog starts, if not the RBS::VERSION of the ref (e.g. 4.1.0)" + required: false + type: string + format: + description: "The changelog template, or the JSON its sections are sorted from" + required: false + default: list + type: choice + options: + - list + - json + +permissions: + contents: read + +jobs: + changelog: + name: changelog + runs-on: ubuntu-latest + permissions: + contents: read # the history and the tags + pull-requests: read # what the commits came from + env: + # The inputs are read through the environment rather than interpolated into + # the script below. + VERSION: ${{ inputs.version }} + FORMAT: ${{ inputs.format }} + GH_TOKEN: ${{ github.token }} + steps: + # `changelog_base` resolves the previous release with `git describe`, the task + # walks the history back to it, and on a release branch it reads the `-x` + # trailers of the commits it finds -- so the full history and the tags are all + # needed. `fetch-depth: 0` fetches both. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + + # Before the gems are installed, since a version that cannot be a version is + # cheapest to reject here. + - name: Check the input + run: | + version_pattern='^[0-9]+\.[0-9]+\.[0-9]+(\.(pre|dev)\.[0-9]+)?$' + if [ -n "$VERSION" ] && ! [[ "$VERSION" =~ $version_pattern ]]; then + echo "::error::\`$VERSION\` is not a version number." + exit 1 + fi + + - name: Set up Ruby + uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0 + with: + ruby-version: ruby + bundler: none + - name: Install gems + run: | + bundle config set --local without libs:profilers + bundle install --jobs 4 --retry 3 + + # Only the changelog goes to STDOUT; the task reports what it is doing, and + # which pull requests it skipped, on STDERR. `tee` keeps both in the log, in + # order, and puts the changelog on its own where the next step can read it. + - name: Print the changelog + run: | + if [ "$FORMAT" = "json" ]; then + task="gem:changelog:json" + else + task="gem:changelog" + fi + if [ -n "$VERSION" ]; then + task="$task[$VERSION]" + fi + + echo "Running \`rake $task\` on $(git rev-parse --abbrev-ref HEAD)" + bundle exec rake "$task" | tee changelog.out + + # Uploaded before it is rendered, so that the changelog survives whatever the + # summary makes of it. This is also what to reach for when the JSON is long + # enough to be truncated in the log. + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: changelog + path: changelog.out + + # The log carries this too, but the summary renders it. A step summary is + # capped at 1MiB and the step fails if that is exceeded, which a long JSON + # could manage -- so past a comfortable fraction of it, link the artifact + # rather than lose the run over formatting. + - name: Render the changelog + run: | + if [ "$(wc -c < changelog.out)" -gt 900000 ]; then + echo "The changelog is too long to render here. Download the \`changelog\` artifact." \ + >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + + { + echo '```markdown' + cat changelog.out + echo '```' + } >> "$GITHUB_STEP_SUMMARY"