Skip to content

Publish a Keep a Changelog file as the release notes - #104

Merged
korya merged 3 commits into
masterfrom
korya-docs-changelog
Aug 30, 2026
Merged

Publish a Keep a Changelog file as the release notes#104
korya merged 3 commits into
masterfrom
korya-docs-changelog

Conversation

@korya

@korya korya commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Problem

Upgrading http-assert means reading its commit log to find out what changed, exit codes included.

Release notes are GitHub's generated commit list, so v0.2.0 shipped 39 subjects — 25 of them docs, chore or test — with the one entry that could break a pipeline sitting at position 23: exit codes 91 and 103 were removed and transport failures moved from 93 to 92. For a tool whose entire contract is its exit code, that is the wrong thing to bury. Nothing about the format can surface it either, because a commit type has no way to say deprecated or breaking, and every docs commit is weighted the same as every fix.

Solution

Add a Keep a Changelog file, backfill it to v0.1.0, and make it the release body so the two cannot drift.

just release-notes <tag> prints one section; goreleaser takes it verbatim through --release-notes, which short-circuits its own generation. The same file ships inside every release archive.

Release body before (generated, v0.3.0):

* chore(go): Raise the module to Go 1.26 and pin the build toolchain by @korya
* chore(ci): Report when the pinned Go toolchain trails its patch line by @korya
* docs(readme): State the Go requirement for a source build by @korya
* chore(deps): Bump actions/setup-go from 5 to 7 by @dependabot
* refactor(assert): Give assertions a kind and failures their parts by @korya
...

Release body after (the file's own section):

### Added
- `--assert-status` accepts a status class (`2xx`), a comma-separated list, an
  inclusive range (`500-503`), or any mix of the three.
- Response bodies encoded with `br` or `zstd` are decoded before the body
  assertions run.
### Changed
- A status spec that can never match is rejected at the flag with exit 71,
  rather than failing an assertion at exit 93.
### Fixed
- A failed header assertion writes values the way a person writes them.

Keeping it honest

A decorative changelog is worse than none — it still reads as the source of truth while telling only part of the story. Two things hold it current, deliberately at different strengths:

Where Strength Why that strength
Tag push, before any artifact is published Fails the release A missing section is cheap to fix while the release is unpublished, and impossible to fix once the tag is on the Go module proxy
Pull request touching *.go without CHANGELOG.md Warns, build stays green Plenty of Go changes are genuinely invisible to a user; failing on those trains everyone to click past the warning that matters. Same reasoning as the existing toolchain-check
sequenceDiagram
    participant Dev
    participant CI as "Release workflow"
    participant GR as "goreleaser"
    participant GH as "GitHub release"
    Dev->>CI: "push tag v0.4.0"
    CI->>CI: "just release-notes v0.4.0"
    Note over CI: "exits 1 if the section is missing or empty"
    CI->>GR: "release --clean --release-notes=..."
    GR->>GH: "publish the section verbatim"
Loading

Why the goreleaser config looks odd

changelog.disable: true is the obvious way to stop the commit dump and is the wrong one: it discards a --release-notes file too and publishes empty notes. The pipe therefore stays enabled and unreachable, with a comment saying so, rather than being deleted or disabled.

The notes file also cannot live in dist/release --clean removes that directory before it reads anything, so a file written there is gone by the time goreleaser looks for it. It goes to the runner's temp directory.

Verified against the real pipeline at v0.3.0 with publishing skipped: goreleaser logs loading file for the extracted section and generates nothing of its own.

Scope of the backfill

v0.1.0 onwards only. Everything up to v0.0.7 predates Conventional Commits and the transfer from PlanitarInc — 48 commits that are mostly merges, Dependabot bumps and four Update README.mds. Honest user-facing entries cannot be recovered from that, and inventing them is the inconsistency the format warns against, so the period is named and pointed at the git history instead.

There is no Security section. The only candidate was a Dependabot bump of the indirect golang.org/x/net, which names no CVE; writing "Security" over an unverified vulnerability is worse than silence. The dependency reduction that is substantiated — viper's removal, 35 of 38 non-stdlib dependencies — is recorded under v0.1.0 as a change.

No visual change: this is a CLI, and the only rendered output that moves is the GitHub release body, shown above.

Other Changes

  • The README download snippet pinned VERSION=v0.1.0, two releases behind, so a reader following it verbatim got a binary without --assert-jq, -L, --retry or brotli, and with the pre-v0.2.0 exit codes. Now v0.3.0, and bumping it is step 3 of the release checklist added to the Development section.
  • CHANGELOG.md is included in every release archive alongside README.md and LICENSE.

🤖 Generated with Claude Code

korya and others added 3 commits August 30, 2026 13:27
The snippet pinned VERSION=v0.1.0, two releases behind, so a reader following the
install instructions verbatim downloaded a binary without --assert-jq, -L, --retry
or brotli support, and with the pre-v0.2.0 exit codes.

The line names a tag rather than resolving the latest one on purpose: the comment
above it already tells the reader to pick their own, and resolving it would mean
parsing the GitHub API with jq, in a README whose argument is that you should not
need jq to check an endpoint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxhyKzFVP99To7pYTxMyua
Release notes were GitHub's generated commit list, which is what this tool's users
are least able to act on. Upgrading from v0.1.0 to v0.2.0 meant reading 39 commit
subjects, 25 of them docs, chore or test, to find the one line that mattered: exit
codes 91 and 103 are gone and transport failures moved from 93 to 92. A tool whose
whole contract is its exit codes had buried a change to them at position 23 of 39.

The format is Keep a Changelog 1.1.0. Its two categories a commit type cannot
express -- Deprecated and Security -- are the two a CLI needs most, and dropping
every docs, chore, test and refactor commit removes 63% of the history as noise.

Backfilled to v0.1.0 only. Everything up to v0.0.7 predates Conventional Commits
and the transfer from PlanitarInc: 48 commits that are mostly merges, Dependabot
bumps and four "Update README.md"s, from which honest user-facing entries cannot
now be recovered. Reconstructing them would invent prose nobody can verify, which
is the inconsistency the format warns against, so that period is named and pointed
at the git history instead.

No Security section. The one candidate was a Dependabot bump of the indirect
golang.org/x/net, which names no CVE; writing "Security" over an unverified
vulnerability is worse than silence. The dependency reduction that is
substantiated -- viper's removal, 35 of 38 non-stdlib dependencies -- is recorded
under v0.1.0 as a change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxhyKzFVP99To7pYTxMyua
CHANGELOG.md is now the release body, so the curated file and the published notes
cannot disagree. `just release-notes <tag>` prints one section and goreleaser takes
it verbatim through --release-notes, which short-circuits its own generation.

A changelog that is merely decorative rots, and the format's own warning is that a
changelog mentioning only some changes is more dangerous than none, because it
still reads as the source of truth. Two things hold it current. The release
extraction fails when the tag's section is missing or empty, which stops the
release before any artifact is published; that is the moment a forgotten entry is
still cheap to fix, unlike a tag already on the module proxy. And a pull request
touching Go sources without touching CHANGELOG.md gets a warning, not a red build,
for the reason toolchain-check warns: plenty of Go changes are genuinely invisible
to a user, and failing on those would train everyone to click past the one that
matters.

Two goreleaser details drove the shape. `changelog.disable: true` looks like the
way to stop the commit dump and is not -- it discards a --release-notes file too
and publishes empty notes -- so the pipe stays enabled and unreachable, documented
in place. And the notes file cannot live in dist/, because `release --clean`
removes that directory before it reads anything; it goes to the runner's temp
directory instead.

Verified against the real pipeline at v0.3.0 with publishing skipped: goreleaser
logs `loading file` for the extracted section and generates nothing of its own.
CHANGELOG.md now also ships inside every release archive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxhyKzFVP99To7pYTxMyua
@korya
korya marked this pull request as ready for review August 30, 2026 17:31
@korya
korya merged commit 1734165 into master Aug 30, 2026
7 checks passed
@korya
korya deleted the korya-docs-changelog branch August 30, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant