Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ on their own GitHub releases.
Sort the list into the sections below. `rake gem:changelog:json` prints the same pull requests with
the changed files, labels, and body of each, which is what the sorting is based on.

Both tasks reach GitHub through `gh`, which a Claude Code on the web session cannot do. See

This comment was marked as off-topic.

[Assembling the changelog without `gh`](#assembling-the-changelog-without-gh) for how the same list
is produced there.

```markdown
## X.Y.Z (YYYY-MM-DD)

Expand Down Expand Up @@ -227,6 +231,81 @@ it, the only pull request a backported commit is associated with is the one that
backport, which says nothing about the change and is the same for every commit it brought over —
that is why the 4.0.3 changelog credits its three entries to the same pull request.

## Assembling the changelog without `gh`

A release can be prepared from a Claude Code on the web session, with one exception:
`gem:changelog` and `gem:changelog:json` cannot run there. Both go through `gh`, and in such a
session `api.github.com` is blocked at the agent proxy for anything the shell does. `gh` is not
installed, installing it does not help, and rewriting the tasks against REST or Net::HTTP would be
blocked the same way — the refusal is keyed on the session rather than on the client:

```console
$ curl -H "Authorization: Bearer $GH_TOKEN" https://api.github.com/repos/ruby/rbs
{"message":"GitHub access is not enabled for this session. ..."} # HTTP 403
```

Nothing else in the release is affected. `gem:check_release` and `gem:tag` read git and the working
tree, and `gem:gh_release` runs on a runner, where `gh` and `github.token` both work.

What the session does have is the GitHub MCP server, which reaches the API through its own
credentials. The changelog is assembled with its tools, in the three steps the rake task takes.

**1. Where the changelog starts.** The rule is `changelog_base`: a prerelease starts from the
latest tag, a release proper skips the prerelease tags. Tags are not fetched by default.

```console
$ git fetch origin --tags
$ git describe --tags --match 'v*' --abbrev=0 --exclude 'v*.pre*' --exclude 'v*.dev*'
v4.1.1
```

Drop the two `--exclude` flags for a prerelease, which starts at the latest tag whatever it is.

**2. The commits.**

```console
$ git log --format=%H v4.1.1..HEAD
```

**3. The pull requests they came from.** List the merged pull requests with `list_pull_requests`
(`base: master`, `state: closed`, `sort: updated`, `direction: desc`, and `fields: number, title,
labels, merged_at, head`), paging back until `merged_at` predates the base tag, and keep the ones
whose `head.sha` appears in the commit list from step 2. That intersection is what the task's
GraphQL `associatedPullRequests` query answers, reached from the other side.

Then drop the pull requests labeled `skip-changelog` and format the rest newest first, which is the
order of step 2:

```markdown
* {title} ([#{number}](https://github.com/ruby/rbs/pull/{number}))
```

Sorting them into sections needs the changed files, which `gem:changelog:json` would have supplied:
`pull_request_read` with `get_files` per pull request, or `get` for the body.

Four things about that matching, the first of which is a trap:

- **Do not read the numbers from `Merge pull request #N` commit subjects.** It looks like it works
on this repository, and it silently loses pull requests. Applying the path filter the task uses
(`git log --full-history --simplify-merges -- . ':(exclude)rust'`) drops the merge commits while
keeping the commits they merged, so on the 4.1.2 cycle five of the eight numbers disappeared with
them. A squashed or rebased pull request never writes that subject at all. Matching `head.sha`
has neither failure mode.
- `head.sha` is in the history because this repository merges pull requests with merge commits. A
squashed or rebased one would need its `merge_commit_sha`, which the listing does not carry.
- The listing reports `merged: false` for pull requests that are merged — the field is not
populated by that endpoint. Read `merged_at` instead.
- On a release branch the commits are cherry-picks, so resolve the `(cherry picked from commit
<sha>)` trailer first and match the recorded origin, as `changelog_origins` does. Matching the
cherry-pick itself attributes every backport to the pull request that carried it.

Pull requests confined to `rust/` are left out too, which the task does by filtering the commits in
step 2 with `-- . ':(exclude)rust'`. Leave step 2 unfiltered here and drop those pull requests by
their `get_files` instead. The filter decides which commits are listed, and a pull request is found
by one specific commit — its head — so a pull request whose last commit happens to touch only
`rust/` would lose that head and disappear even though the rest of it belongs in the changelog.
Matching against every commit and filtering afterwards cannot go wrong that way.

## Notes

- Prereleases (`X.Y.Z.pre.N`) are only installed with `gem install rbs --pre`;
Expand Down
Loading