From 0485fd0b6cb42023d52f63311ed57870cb8f9dde Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 6 Jul 2026 16:37:43 -0400 Subject: [PATCH] Guard published-docs updates by release line (#4440) --- .github/workflows/publish-fastmcp.yml | 14 ++++++++++++++ CLAUDE.md | 10 ++++++---- docs/development/releases.mdx | 2 ++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-fastmcp.yml b/.github/workflows/publish-fastmcp.yml index 4361e4bb2..3e22fe915 100644 --- a/.github/workflows/publish-fastmcp.yml +++ b/.github/workflows/publish-fastmcp.yml @@ -133,5 +133,19 @@ jobs: fetch-depth: 0 ref: ${{ github.event.workflow_run.head_sha }} + - name: Check release line + id: release_line + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + run: | + git fetch origin "${DEFAULT_BRANCH}:refs/remotes/origin/${DEFAULT_BRANCH}" + if git merge-base --is-ancestor HEAD "refs/remotes/origin/${DEFAULT_BRANCH}"; then + echo "update_published_docs=true" >> "$GITHUB_OUTPUT" + else + echo "update_published_docs=false" >> "$GITHUB_OUTPUT" + echo "Release commit is not on ${DEFAULT_BRANCH}; skipping published-docs update." + fi + - name: Point published-docs at published release + if: steps.release_line.outputs.update_published_docs == 'true' run: git push --force origin "HEAD:published-docs" diff --git a/CLAUDE.md b/CLAUDE.md index a71054b93..e900cc9c7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -84,12 +84,12 @@ Only cut releases when the maintainer explicitly asks. Tags follow `v` Write the maintainer-approved handwritten notes to a temporary file, then create the release. `--generate-notes` appends the auto-generated changelog after the handwritten content. ```bash -gh release create v3.2.0 --target main --title "v3.2.0: Theme Here" --generate-notes --notes-start-tag v3.1.1 --notes-file /tmp/release-notes.md +gh release create v4.0.0 --target main --title "v4.0.0: Theme Here" --generate-notes --notes-start-tag v3.4.4 --notes-file /tmp/release-notes.md ``` **Always pass `--notes-start-tag `.** Without it, `--generate-notes` picks the most recent prior tag as the changelog start point — and if a prerelease exists (e.g. `v3.4.0b1`), it starts from *that*, silently truncating the PR list to only the commits since the beta. Pin it to the last stable release (e.g. `v3.3.1` when cutting `v3.4.0`). Verify after: the compare link at the bottom of the generated notes should read `v...v`. -Most releases target `main`, but maintenance or backport releases may target a different branch (e.g., `release/2.x`). Confirm the target with the maintainer if there's any ambiguity. +Use the branch that owns the release line as the target: current-major releases target `main`, 3.x maintenance releases target `release/3.x`, and 2.x maintenance releases target `release/2.x`. Confirm the target with the maintainer if there's any ambiguity. For example, cut a 3.4.4 maintenance release with `--target release/3.x`, not `main`. The handwritten notes are prepended above the auto-generated changelog and are the part that matters. Do not include a title in the notes body — the release title (`v{version}: {pun}`) already serves as the heading. Work with the maintainer to draft the notes — propose a draft, get feedback, iterate. Do not publish without the maintainer's sign-off. @@ -105,16 +105,18 @@ gh api -X POST repos/PrefectHQ/fastmcp/releases/generate-notes \ --jq '.body' ``` +Set `target_commitish` to the same branch that will receive the release tag. For maintenance releases, use the maintenance branch (for example, `release/3.x`) so the preview matches the release notes GitHub will generate. + **Point releases** (3.0, 3.1, 3.2) get narrative prose: open with the theme of the release, then walk through headline features conceptually — what they enable, why they matter, how they fit together. Write it the way a blog post reads, not a changelog. Multiple paragraphs, code examples where they clarify. **Patch releases** (3.1.1, 3.0.2) get 1-2 sentences explaining what broke and what the fix does. Keep it minimal — the auto-generated changelog has the details. -**Merge the docs changelog PR *before* cutting the release, not after.** The post-publish `update-published-docs` job force-pushes the `published-docs` branch (which gofastmcp.com serves) to the *released commit* — so the changelog entry only reaches the live site if it's already in the commit being tagged. Land the docs PR on `main` first, then cut the release from `main`. If you tag first and merge docs after, this release's changelog won't appear on the live site until the *next* release force-pushes `published-docs` forward. Two hand-maintained files mirror the GitHub release and must get a new entry for every version, newest at the top (these are `.mdx` and are not covered by the prek Prettier hook, which only runs on `yaml`/`json5` — match the existing entries' style by hand): +**Merge the docs changelog PR *before* cutting the release, not after.** The post-publish `update-published-docs` job force-pushes the `published-docs` branch (which gofastmcp.com serves) to the released commit for stable releases on the default branch, so the changelog entry only reaches the live site if it's already in the commit being tagged. Land the docs PR on the release target branch first, then cut the release from that branch. If you tag first and merge docs after, this release's changelog won't appear on the live site until the next default-branch stable release force-pushes `published-docs` forward. Maintenance releases from `release/3.x` or `release/2.x` publish packages and GitHub notes without repointing `published-docs`; add their changelog entries on the maintenance branch, slotted into the matching major-version section. Two hand-maintained files mirror the GitHub release and must get a new entry for every version, newest at the top (these are `.mdx` and are not covered by the prek Prettier hook, which only runs on `yaml`/`json5` — match the existing entries' style by hand): - `docs/changelog.mdx` is the full mirror. Add an `` block with: a bold linked title (`**[v: ]()**`), a condensed 1-paragraph intro (one sentence for patches), the full categorized PR list reformatted from the `--generate-notes` output (`* by [@user](https://github.com/user) in [#NNNN](<pull-url>)`), a `## New Contributors` list (plain `@user`, linked PR), and a `**Full Changelog**: [vA...vB](<compare-url>)` line. - `docs/updates.mdx` is the skimmable card feed. Add an `<Update label="FastMCP <version>" description="Month DD, YYYY" tags={["Releases"]}>` wrapping a `<Card>` that links to the GitHub release, with a 1-2 sentence summary and (for point releases) a handful of emoji-bulleted highlights. -Because the docs land *before* the tag exists, derive the entry from the maintainer-approved handwritten notes (intro/summary) and the `--generate-notes` API *preview* (the PR-list body — see the generate-notes API call above, which returns the exact changelog without cutting anything). Scripting the link reformatting is reliable for long PR lists. The release-URL, tag, and compare links follow the known pattern (`/releases/tag/v<version>`, `compare/v<last-stable>...v<version>`) and will 404 only during the short window between merging the docs PR and cutting the release minutes later — they resolve before `published-docs` ever deploys, since that happens after the full publish chain. For this reason, create and merge the docs PR *immediately* before cutting the release — treat the two as one tight back-to-back sequence, not independent steps — so the links are valid by the time the release publishes rather than dangling for any longer than necessary. Maintenance/backport releases (e.g. `v2.14.7`) get an entry in the same two files, slotted into the 2.x section. +Because the docs land *before* the tag exists, derive the entry from the maintainer-approved handwritten notes (intro/summary) and the `--generate-notes` API *preview* (the PR-list body — see the generate-notes API call above, which returns the exact changelog without cutting anything). Scripting the link reformatting is reliable for long PR lists. The release-URL, tag, and compare links follow the known pattern (`/releases/tag/v<version>`, `compare/v<last-stable>...v<version>`) and will 404 only during the short window between merging the docs PR and cutting the release minutes later — they resolve before the release workflow completes. For this reason, create and merge the docs PR *immediately* before cutting the release — treat the two as one tight back-to-back sequence, not independent steps — so the links are valid by the time the release publishes rather than dangling for any longer than necessary. ### Commit Messages and Agent Attribution diff --git a/docs/development/releases.mdx b/docs/development/releases.mdx index 727beb74d..3740a3c22 100644 --- a/docs/development/releases.mdx +++ b/docs/development/releases.mdx @@ -65,6 +65,8 @@ Our release process is intentionally simple: 2. Generate release notes automatically, and curate or add additional editorial information as needed 3. GitHub releases automatically trigger PyPI deployments +Current-major releases target `main`. Maintenance releases target their release branch, such as `release/3.x` for 3.x patches and `release/2.x` for 2.x patches. Stable releases from `main` update the `published-docs` branch after PyPI publishing succeeds; maintenance releases publish packages and GitHub release notes without repointing the live docs branch. + This automation lets maintainers focus on code quality rather than release mechanics. ### Release Cadence