From acdf4601327e57cdbe6259a83ead407a0fed3e22 Mon Sep 17 00:00:00 2001 From: Thiago Durante Date: Thu, 6 Aug 2026 09:16:52 +0200 Subject: [PATCH 1/2] docs: document the changelog convention and the release process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neither was written down. The release mechanism had to be reverse-engineered from tag objects and workflow config to answer "does merging bump a version?" (it does not), and the changelog convention was inconsistent across the only two releases that have one: v0.20.0 came in via a PR, v0.20.1 via a direct push to main, and in both cases the whole section was authored at release time rather than accumulated. Establishes the accumulating convention: every PR that changes observable behaviour adds its entry under `## [Unreleased]`, so releasing is renaming a heading rather than reconstructing history from the log. Calls out that `skills/` is go:embed-ed, so a reference-doc correction ships to users and warrants an entry — that is not obvious from the file layout. Also records the parts that are easy to get wrong: - Releases are manual and tag-driven; merging to main publishes nothing. - The version lives only in the git tag. GoReleaser injects it via ldflags and local builds report `dev`, so there is no version file to bump. - GoReleaser's `changelog:` block generates the GitHub release notes from commit subjects and never touches CHANGELOG.md. It also filters out `docs:`, `chore:`, `test:` and `ci:` — which is why the two existing CHANGELOG commits, both `docs:`, do not appear in any release notes. - `internal/version/update.go` polls the releases API, so tagging immediately advertises the upgrade to every installed CLI. Deploy dependent API changes before tagging, not after. Adds the changelog step to the Adding-a-New-Resource checklist. AGENTS.md is a symlink to this file, so agents pick it up too. Every claim verified against .goreleaser.yaml, .github/workflows/release.yml, cmd/dhq/main.go, internal/version/update.go, skills/embed.go and the annotated tag objects. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Hibw5xsRNGDkYzr1hXsDQz --- CLAUDE.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 6cc2e99..394a4e6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -90,6 +90,41 @@ All handled by `FlexString` or `[]interface{}`. 3. Add CLI command in `internal/commands/` 4. Register in `internal/commands/root.go` 5. Add tests +6. Add a `CHANGELOG.md` entry under `## [Unreleased]` (see Changelog & Releases) + +## Changelog & Releases + +**Every PR that changes observable behaviour adds an entry under `## [Unreleased]` +in `CHANGELOG.md`**, grouped under `### Added` / `### Changed` / `### Fixed` / +`### Breaking (SDK)`. Entries accumulate there across PRs; releasing is then just +renaming the heading. Note `skills/` is `go:embed`-ed into the binary, so a skill +or reference-doc correction ships to users and warrants an entry — only genuinely +internal changes (refactors, test-only work, CI config) can skip it. + +Releasing is **manual and tag-driven**. Merging to `main` publishes nothing. + +1. Rename `## [Unreleased]` to `## [X.Y.Z] - YYYY-MM-DD`. +2. Update the compare links at the bottom of the file: + - `[Unreleased]: https://github.com/deployhq/deployhq-cli/compare/vX.Y.Z...HEAD` + - `[X.Y.Z]: https://github.com/deployhq/deployhq-cli/releases/tag/vX.Y.Z` +3. Commit on `main`. +4. `git tag -a vX.Y.Z -m "vX.Y.Z" && git push origin vX.Y.Z` + +The tag push is what triggers `release.yml` → GoReleaser. Tags are annotated +(`-a`) by convention. + +**The version is not stored in the repo.** GoReleaser injects it from the tag +(`-X main.version={{.Version}}`); `cmd/dhq/main.go` defaults to `dev` for local +builds. There is no version file to bump. + +Two things that are easy to conflate: + +- GoReleaser's `changelog:` block in `.goreleaser.yaml` generates the **GitHub + release notes** from commit subjects. It never reads or writes `CHANGELOG.md`, + and it filters out `docs:`, `chore:`, `test:` and `ci:` commits. +- `internal/version/update.go` polls the GitHub releases API, so tagging + immediately advertises the upgrade to every installed CLI. Don't tag ahead of + an API change the release depends on — deploy the backend first. ## Distribution From b3e7a2706a41a39712b6f7875ae22a421e3453b7 Mon Sep 17 00:00:00 2001 From: Thiago Durante Date: Thu, 6 Aug 2026 09:35:31 +0200 Subject: [PATCH 2/2] docs: push main before the tag, and correct the update-visibility timing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review follow-up on #41. Two defects in the runbook, both of which matter because the whole point of the doc is that someone follows it literally. The release steps committed the changelog on main and then pushed only the tag. Pushing a tag sends the reachable objects but does not move refs/heads/main, so following the sequence verbatim would publish a release containing the changelog while origin/main still lacked it. Adds the explicit `git push origin main`, before the tag, with a note on why the order matters. The update-checker note claimed tagging "immediately advertises the upgrade to every installed CLI". internal/version/update.go:14 reads /releases/latest — the latest *published* release, not the tag — and Check() runs per CLI invocation. The real sequence is tag, GoReleaser builds, release published, then each CLI notices on its next run. The practical advice is unchanged (deploy dependent API changes before tagging) but the stated mechanism now matches the code. Caught by CodeRabbit and Codex on #41. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Hibw5xsRNGDkYzr1hXsDQz --- CLAUDE.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 394a4e6..aee6ba3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -107,9 +107,13 @@ Releasing is **manual and tag-driven**. Merging to `main` publishes nothing. 2. Update the compare links at the bottom of the file: - `[Unreleased]: https://github.com/deployhq/deployhq-cli/compare/vX.Y.Z...HEAD` - `[X.Y.Z]: https://github.com/deployhq/deployhq-cli/releases/tag/vX.Y.Z` -3. Commit on `main`. +3. Commit on `main` and **push it**: `git push origin main`. 4. `git tag -a vX.Y.Z -m "vX.Y.Z" && git push origin vX.Y.Z` +Push `main` *before* the tag. Pushing a tag sends the reachable objects but does +not move `refs/heads/main`, so tagging first leaves the release containing the +changelog while `origin/main` still lacks it. + The tag push is what triggers `release.yml` → GoReleaser. Tags are annotated (`-a`) by convention. @@ -122,9 +126,11 @@ Two things that are easy to conflate: - GoReleaser's `changelog:` block in `.goreleaser.yaml` generates the **GitHub release notes** from commit subjects. It never reads or writes `CHANGELOG.md`, and it filters out `docs:`, `chore:`, `test:` and `ci:` commits. -- `internal/version/update.go` polls the GitHub releases API, so tagging - immediately advertises the upgrade to every installed CLI. Don't tag ahead of - an API change the release depends on — deploy the backend first. +- `internal/version/update.go` reads `/releases/latest`, i.e. the latest + *published* release — not the tag. So the sequence is: tag → GoReleaser builds + → release published → each installed CLI notices on its **next invocation**. + Not instant, but not something you can take back either: don't tag ahead of an + API change the release depends on — deploy the backend first. ## Distribution