From cc500c7f3922336fa9985178ecab5b4adddb307c Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 4 Jul 2026 23:45:58 -0700 Subject: [PATCH 1/4] feat(release): include a categorized PR changelog in orb-v release notes The GitHub Release step dropped `--generate-notes` outright after the very first orb release hit GitHub's 125000-character release-body limit -- with no prior orb-v tag to bound the diff, the generated notes spanned the entire repo history. Every release from orb-v0.1.0 onward has a real previous tag, so scope the changelog explicitly to the range since the immediately preceding orb-v tag (found by tag-creation order, not a semver sort that would mis-order a prerelease against its own later stable release, and not the Release Notes API's own "previous release" auto-detection, since this repo's release list also carries unrelated mcp-v* releases on an independent cadence). Add .github/release.yml so the generated changelog groups PRs into Features/Fixes/Other Changes using the gittensor:feature/gittensor:bug labels gittensory's own review engine already applies to every merged PR -- no separate manual-labeling process needed. Keep a safety net for the original failure mode: if the combined notes would still exceed GitHub's limit, or the generate-notes call itself fails, fall back to the plain pull-command notes (optionally with a compare-view link) instead of ever blocking the release. --- .github/release.yml | 21 +++ .github/workflows/release-selfhost.yml | 47 +++++- test/unit/release-selfhost-notes.test.ts | 189 +++++++++++++++++++++++ 3 files changed, 249 insertions(+), 8 deletions(-) create mode 100644 .github/release.yml create mode 100644 test/unit/release-selfhost-notes.test.ts diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000000..6c84e92e8d --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,21 @@ +# Categorization for GitHub's auto-generated release notes on orb-v tags. The release-orb +# workflow (.github/workflows/release-selfhost.yml) calls the "Generate release notes content" API +# with this repo's default branch as context, which groups merged PRs since the previous orb-v tag +# into the categories below by label. `gittensor:feature` and `gittensor:bug` are applied +# automatically by gittensory's own review engine on every merged PR, so this works without any +# separate manual-labeling process. +changelog: + exclude: + authors: + - dependabot + - github-actions + categories: + - title: ๐Ÿš€ Features + labels: + - "gittensor:feature" + - title: ๐Ÿ› Fixes + labels: + - "gittensor:bug" + - title: ๐Ÿงน Other Changes + labels: + - "*" diff --git a/.github/workflows/release-selfhost.yml b/.github/workflows/release-selfhost.yml index bb3ff1cec3..15ceb4e3aa 100644 --- a/.github/workflows/release-selfhost.yml +++ b/.github/workflows/release-selfhost.yml @@ -308,21 +308,52 @@ jobs: if [ "$PRERELEASE" = "true" ]; then PRERELEASE_ARGS=(--prerelease --latest=false) fi - # `--generate-notes` appends GitHub's auto-generated "what's changed" body (every commit/PR - # description since the last release) on top of `$NOTES` -- confirmed by hitting GitHub's - # 125000-char release-body limit cutting the very first release here, where a single - # squash-merged PR's body alone was large. `$NOTES` (the pull command + version metadata) is - # what operators actually need; drop `--generate-notes` so an unusually large commit/PR history - # can never fail release creation outright. + # Find the orb-v tag immediately preceding this one, by actual tag-creation order -- NOT a + # semver sort (which mis-orders a prerelease against its own later stable tag, e.g. + # 0.1.0-beta.2 vs 0.1.0), and NOT the Release Notes API's own "previous release" auto-detection + # either (this repo's release list also carries mcp-v* releases on an independent cadence, which + # could get picked up by mistake once the two schemes' timestamps interleave). Excludes the tag + # being released so a workflow re-run never diffs a tag against itself. + PREV_TAG=$(git tag -l 'orb-v*' --sort=-creatordate | grep -vF -x "$REF_NAME" | head -1 || true) + + # The very first orb release here hit GitHub's 125000-character release-body limit + # generating notes across the ENTIRE repo history, because no prior orb-v tag existed yet to + # bound the diff -- that's why `--generate-notes` was dropped entirely rather than just + # pinning its range. Every release from orb-v0.1.0 onward has a real previous tag (confirmed: + # orb-v0.1.0 -> orb-v0.2.0 generates ~11K chars, comfortably under the limit), so fetch the + # PR changelog explicitly scoped to that range via the same API `--generate-notes` uses, + # categorized by `.github/release.yml`. PREV_TAG empty (no prior orb-v tag) skips this and + # falls back to the plain notes below, the same as the original safe behavior. + CHANGELOG="" + if [ -n "$PREV_TAG" ]; then + CHANGELOG=$(gh api "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \ + -f tag_name="$REF_NAME" \ + -f previous_tag_name="$PREV_TAG" \ + --jq '.body' 2>/dev/null || true) + fi + + FULL_NOTES="$NOTES" + if [ -n "$CHANGELOG" ]; then + FULL_NOTES="${NOTES}"$'\n\n'"${CHANGELOG}" + fi + + # Never let an outlier changelog (an unusually large PR history, or a future change to what + # the categorization API returns) block publishing the image itself -- fall back to the plain + # pull-command notes plus a compare link if the combined body would exceed GitHub's limit. + if [ "${#FULL_NOTES}" -gt 120000 ]; then + echo "::warning::Generated release notes would be ${#FULL_NOTES} chars, near GitHub's 125000 release-body limit -- falling back to the plain notes without the changelog." + FULL_NOTES="${NOTES}"$'\n\n'"_Changelog omitted (too large for a GitHub Release body) -- see https://github.com/${GITHUB_REPOSITORY}/compare/${PREV_TAG}...${REF_NAME}_" + fi + if gh release view "$REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then gh release edit "$REF_NAME" --repo "$GITHUB_REPOSITORY" \ --title "gittensory-orb ${RELEASE_TAG}" \ - --notes "$NOTES" \ + --notes "$FULL_NOTES" \ "${PRERELEASE_ARGS[@]}" else gh release create "$REF_NAME" --repo "$GITHUB_REPOSITORY" \ --verify-tag \ --title "gittensory-orb ${RELEASE_TAG}" \ "${PRERELEASE_ARGS[@]}" \ - --notes "$NOTES" + --notes "$FULL_NOTES" fi diff --git a/test/unit/release-selfhost-notes.test.ts b/test/unit/release-selfhost-notes.test.ts new file mode 100644 index 0000000000..2019565cbf --- /dev/null +++ b/test/unit/release-selfhost-notes.test.ts @@ -0,0 +1,189 @@ +import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { spawnSync } from "node:child_process"; +import { afterEach, describe, expect, it } from "vitest"; +import { parse } from "yaml"; + +const tmpDirs: string[] = []; +afterEach(() => { + for (const dir of tmpDirs.splice(0)) rmSync(dir, { recursive: true, force: true }); +}); + +// Extracted straight out of the committed workflow YAML (same technique as the "Resolve version" step +// tests in release-selfhost-prerelease.test.ts), so a regression in the actual bash fails this test +// instead of only surfacing on a real tag push. +function readGithubReleaseStep(): string { + const workflow = parse(readFileSync(".github/workflows/release-selfhost.yml", "utf8")) as { + jobs: { release: { steps: Array<{ name?: string; run?: string }> } }; + }; + const step = workflow.jobs.release.steps.find((s) => s.name === "GitHub Release"); + if (!step?.run) throw new Error('step "GitHub Release" not found or has no run: block'); + return step.run; +} + +interface HarnessOptions { + /** Lines "git tag -l 'orb-v*' --sort=-creatordate" should print, newest first. */ + tagList: string[]; + /** Body the fake "gh api .../releases/generate-notes" call returns; omit to simulate the API failing. */ + changelogBody?: string; + /** Whether "gh release view" should report the release as already existing (drives create vs edit). */ + releaseExists?: boolean; +} + +function createHarness(options: HarnessOptions) { + const dir = mkdtempSync(join(tmpdir(), "gtorb-release-notes-")); + const binDir = join(dir, "bin"); + const callsLog = join(dir, "calls.log"); + const notesFile = join(dir, "notes-passed.txt"); + mkdirSync(binDir); + tmpDirs.push(dir); + + writeFileSync(join(dir, "changelog-body.json"), JSON.stringify({ body: options.changelogBody ?? "" })); + + writeFileSync( + join(binDir, "git"), + `#!/usr/bin/env bash +set -euo pipefail +printf 'git %s\\n' "$*" >> "$CALLS_LOG" +if [ "$1" = "tag" ]; then + printf '%s\\n' ${options.tagList.map((t) => `"${t}"`).join(" ")} + exit 0 +fi +printf 'unexpected git invocation: %s\\n' "$*" >&2 +exit 1 +`, + ); + chmodSync(join(binDir, "git"), 0o755); + + const generateNotesExit = options.changelogBody === undefined ? 1 : 0; + writeFileSync( + join(binDir, "gh"), + `#!/usr/bin/env bash +set -euo pipefail +printf 'gh %s\\n' "$*" >> "$CALLS_LOG" +if [ "$1" = "api" ]; then + if [ "${generateNotesExit}" = "1" ]; then + exit 1 + fi + node -e "process.stdout.write(JSON.parse(require('fs').readFileSync(process.env.CHANGELOG_FILE, 'utf8')).body)" + exit 0 +fi +if [ "$1" = "release" ] && [ "$2" = "view" ]; then + exit ${options.releaseExists ? "0" : "1"} +fi +if [ "$1" = "release" ] && { [ "$2" = "create" ] || [ "$2" = "edit" ]; }; then + prev="" + for arg in "$@"; do + if [ "$prev" = "--notes" ]; then + printf '%s' "$arg" > "$NOTES_FILE" + fi + prev="$arg" + done + exit 0 +fi +printf 'unexpected gh invocation: %s\\n' "$*" >&2 +exit 1 +`, + ); + chmodSync(join(binDir, "gh"), 0o755); + + return { + dir, + run() { + const run = readGithubReleaseStep(); + const result = spawnSync("bash", ["-c", run], { + encoding: "utf8", + env: { + ...process.env, + PATH: `${binDir}:${process.env.PATH ?? ""}`, + CALLS_LOG: callsLog, + NOTES_FILE: notesFile, + CHANGELOG_FILE: join(dir, "changelog-body.json"), + GH_TOKEN: "test-token", + GITHUB_REPOSITORY: "JSONbored/gittensory", + REF_NAME: "orb-v0.2.0", + RELEASE_VERSION: "0.2.0", + RELEASE_TAG: "orb-v0.2.0", + RELEASE_ID: "gittensory-orb@0.2.0", + REPOSITORY_OWNER: "JSONbored", + PRERELEASE: "false", + }, + }); + return { + status: result.status, + stderr: result.stderr, + calls: readOptional(callsLog), + notesPassed: readOptional(notesFile), + }; + }, + }; +} + +function readOptional(path: string): string { + try { + return readFileSync(path, "utf8"); + } catch { + return ""; + } +} + +describe('release-selfhost.yml "GitHub Release" step changelog generation', () => { + it("appends the generated changelog to the plain notes when a previous orb-v tag exists", () => { + const harness = createHarness({ + tagList: ["orb-v0.2.0", "orb-v0.1.0", "orb-v0.1.0-beta.2", "orb-v0.1.0-beta.1"], + changelogBody: "## What's Changed\n* feat: something by @someone in .../pull/1", + }); + const r = harness.run(); + expect(r.status).toBe(0); + expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/gittensory-selfhost:orb-v0.2.0"); + expect(r.notesPassed).toContain("## What's Changed"); + expect(r.notesPassed).toContain("feat: something"); + // The tag being released must never be diffed against itself. + expect(r.calls).toContain("previous_tag_name=orb-v0.1.0"); + expect(r.calls).not.toContain("previous_tag_name=orb-v0.2.0"); + }); + + it("falls back to the plain notes with no changelog section when there is no prior orb-v tag", () => { + // The very-first-release case: the tag list contains only the tag being released, so PREV_TAG + // resolves empty and the generate-notes call must be skipped entirely (not even attempted). + const harness = createHarness({ tagList: ["orb-v0.2.0"], changelogBody: "## What's Changed\n* whatever" }); + const r = harness.run(); + expect(r.status).toBe(0); + expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/gittensory-selfhost:orb-v0.2.0"); + expect(r.notesPassed).not.toContain("What's Changed"); + expect(r.calls).not.toContain("gh api"); + }); + + it("falls back to a compare-link note instead of a changelog that would exceed GitHub's release-body limit", () => { + const harness = createHarness({ + tagList: ["orb-v0.2.0", "orb-v0.1.0"], + changelogBody: "x".repeat(121000), + }); + const r = harness.run(); + expect(r.status).toBe(0); + expect(r.notesPassed).not.toContain("xxxx"); + expect(r.notesPassed).toContain("Changelog omitted"); + expect(r.notesPassed).toContain("https://github.com/JSONbored/gittensory/compare/orb-v0.1.0...orb-v0.2.0"); + expect(r.notesPassed.length).toBeLessThan(121000); + }); + + it("uses release create when the release does not exist yet, and release edit when it does", () => { + const notYetReleased = createHarness({ tagList: ["orb-v0.2.0", "orb-v0.1.0"], changelogBody: "notes", releaseExists: false }); + const r1 = notYetReleased.run(); + expect(r1.calls).toContain("gh release create orb-v0.2.0"); + expect(r1.calls).not.toContain("gh release edit"); + + const alreadyReleased = createHarness({ tagList: ["orb-v0.2.0", "orb-v0.1.0"], changelogBody: "notes", releaseExists: true }); + const r2 = alreadyReleased.run(); + expect(r2.calls).toContain("gh release edit orb-v0.2.0"); + expect(r2.calls).not.toContain("gh release create"); + }); + + it("still publishes with the plain notes if the generate-notes API call itself fails", () => { + const harness = createHarness({ tagList: ["orb-v0.2.0", "orb-v0.1.0"] }); // changelogBody omitted -> API call exits 1 + const r = harness.run(); + expect(r.status).toBe(0); + expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/gittensory-selfhost:orb-v0.2.0"); + }); +}); From 9627bd7ecd3c9003067a93201695cf7069cb7c7f Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 4 Jul 2026 23:59:49 -0700 Subject: [PATCH 2/4] fix(release): tighten tag-order wording and warn on changelog fetch failure Review nits on the changelog PR: the "actual tag-creation order" comment overclaimed precision --sort=-creatordate can't guarantee for two lightweight orb-v tags pointing at the same commit (e.g. promoting an -rc straight to stable with no new commits); reword it as the best available local tag-date ordering and document that the only consequence of a tie is an emptier-than-expected changelog for that one release, never an incorrect or blocked one. Also warn in the run log when the generate-notes API call itself fails, so an operator can tell that apart from a genuinely empty PR range between two tags. --- .github/workflows/release-selfhost.yml | 28 +++++++++++++++++------- test/unit/release-selfhost-notes.test.ts | 4 ++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release-selfhost.yml b/.github/workflows/release-selfhost.yml index 15ceb4e3aa..e533969160 100644 --- a/.github/workflows/release-selfhost.yml +++ b/.github/workflows/release-selfhost.yml @@ -308,12 +308,18 @@ jobs: if [ "$PRERELEASE" = "true" ]; then PRERELEASE_ARGS=(--prerelease --latest=false) fi - # Find the orb-v tag immediately preceding this one, by actual tag-creation order -- NOT a - # semver sort (which mis-orders a prerelease against its own later stable tag, e.g. - # 0.1.0-beta.2 vs 0.1.0), and NOT the Release Notes API's own "previous release" auto-detection - # either (this repo's release list also carries mcp-v* releases on an independent cadence, which - # could get picked up by mistake once the two schemes' timestamps interleave). Excludes the tag - # being released so a workflow re-run never diffs a tag against itself. + # Find the orb-v tag immediately preceding this one, by the best available local tag-date + # ordering (`--sort=-creatordate`: the tag's own date for an annotated tag, or its tagged + # commit's date for a lightweight one) -- NOT a semver sort, which mis-orders a prerelease + # against its own later stable tag (e.g. 0.1.0-beta.2 vs 0.1.0), and NOT the Release Notes + # API's own "previous release" auto-detection either (this repo's release list also carries + # mcp-v* releases on an independent cadence, which could get picked up by mistake once the two + # schemes' timestamps interleave). This is NOT a guarantee of true creation order for two + # lightweight orb-v tags pointing at the SAME commit (e.g. promoting an -rc straight to stable + # with no new commits) -- that tie is broken by git's own secondary sort, not by which tag was + # actually pushed first. Not a correctness problem in practice: the changelog for that specific + # release would just come out emptier than expected, never wrong or release-blocking. Excludes + # the tag being released so a workflow re-run never diffs a tag against itself. PREV_TAG=$(git tag -l 'orb-v*' --sort=-creatordate | grep -vF -x "$REF_NAME" | head -1 || true) # The very first orb release here hit GitHub's 125000-character release-body limit @@ -326,10 +332,16 @@ jobs: # falls back to the plain notes below, the same as the original safe behavior. CHANGELOG="" if [ -n "$PREV_TAG" ]; then - CHANGELOG=$(gh api "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \ + if ! CHANGELOG=$(gh api "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \ -f tag_name="$REF_NAME" \ -f previous_tag_name="$PREV_TAG" \ - --jq '.body' 2>/dev/null || true) + --jq '.body' 2>/dev/null); then + # Fails open below (plain notes, no changelog) either way -- this warning just tells an + # operator reading the run log that the section is missing because the API call itself + # failed, not because the range between PREV_TAG and this release genuinely had no PRs. + echo "::warning::Fetching the release changelog (${PREV_TAG}...${REF_NAME}) failed; publishing without it." + CHANGELOG="" + fi fi FULL_NOTES="$NOTES" diff --git a/test/unit/release-selfhost-notes.test.ts b/test/unit/release-selfhost-notes.test.ts index 2019565cbf..e1974a3efb 100644 --- a/test/unit/release-selfhost-notes.test.ts +++ b/test/unit/release-selfhost-notes.test.ts @@ -112,6 +112,7 @@ exit 1 }); return { status: result.status, + stdout: result.stdout, stderr: result.stderr, calls: readOptional(callsLog), notesPassed: readOptional(notesFile), @@ -185,5 +186,8 @@ describe('release-selfhost.yml "GitHub Release" step changelog generation', () = const r = harness.run(); expect(r.status).toBe(0); expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/gittensory-selfhost:orb-v0.2.0"); + // A silent empty changelog would be indistinguishable from a genuinely empty PR range -- the run + // log must say the API call itself failed. + expect(r.stdout).toContain("::warning::Fetching the release changelog"); }); }); From 5d899efff77c4c1f443a9a1496c4239d863a5efb Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sun, 5 Jul 2026 00:13:30 -0700 Subject: [PATCH 3/4] fix(release): exclude bots by their actual [bot]-suffixed GitHub login The exclude.authors list used bare names (dependabot, github-actions), but GitHub's changelog-generation API matches the literal API login, which for a bot account includes the [bot] suffix -- confirmed against this repo's own PR history, where dependabot's login is dependabot[bot]. The bare names never matched anything, so the exclusion was a no-op. Also add renovate[bot] (17 merged PRs here, the same routine-dependency- bump noise dependabot produces) to the list. Deliberately leave sentry[bot] and gittensory-orb[bot] unexcluded: both produce real, gittensor:bug/feature-labeled changes worth surfacing (e.g. #3305, a Seer-authored fix for a real production error), not noise. Also strengthen the oversized-changelog fallback test to assert the operator-critical pull command survives alongside the compare-link fallback, not just that the oversized filler text is gone. --- .github/release.yml | 13 +++++++++++-- test/unit/release-selfhost-notes.test.ts | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/release.yml b/.github/release.yml index 6c84e92e8d..b359808c91 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -4,11 +4,20 @@ # into the categories below by label. `gittensor:feature` and `gittensor:bug` are applied # automatically by gittensory's own review engine on every merged PR, so this works without any # separate manual-labeling process. +# +# `exclude.authors` matches the exact GitHub API login, which for a bot account includes the `[bot]` +# suffix (confirmed against this repo's own PR history: dependabot's login is literally +# `dependabot[bot]`, not `dependabot`) -- excludes the two pure dependency-bump bots so routine version +# bumps don't clutter an operator-facing image changelog. Deliberately does NOT exclude +# `sentry[bot]` (Seer-authored fixes for real production errors, e.g. #3305, get a normal +# `gittensor:bug` label and are exactly the kind of change this changelog exists to surface) or +# `gittensory-orb[bot]` (the review engine's own rare, real content changes, e.g. #3397). changelog: exclude: authors: - - dependabot - - github-actions + - dependabot[bot] + - renovate[bot] + - github-actions[bot] categories: - title: ๐Ÿš€ Features labels: diff --git a/test/unit/release-selfhost-notes.test.ts b/test/unit/release-selfhost-notes.test.ts index e1974a3efb..ffaa373b62 100644 --- a/test/unit/release-selfhost-notes.test.ts +++ b/test/unit/release-selfhost-notes.test.ts @@ -167,6 +167,8 @@ describe('release-selfhost.yml "GitHub Release" step changelog generation', () = expect(r.notesPassed).toContain("Changelog omitted"); expect(r.notesPassed).toContain("https://github.com/JSONbored/gittensory/compare/orb-v0.1.0...orb-v0.2.0"); expect(r.notesPassed.length).toBeLessThan(121000); + // The fallback must not drop the operator-critical pull command along with the oversized changelog. + expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/gittensory-selfhost:orb-v0.2.0"); }); it("uses release create when the release does not exist yet, and release edit when it does", () => { From 142e8e743bdd98d7031ed8fcba277519afe599d2 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sun, 5 Jul 2026 00:25:39 -0700 Subject: [PATCH 4/4] fix(release): correct exclude-list comment count and lock the API range The comment said "the two pure dependency-bump bots" but the list has three entries since renovate[bot] was added alongside dependabot[bot]; reword to explain github-actions[bot]'s inclusion (no PRs here today, but the same operator-facing noise if that changes) instead of miscounting. Also tighten the changelog test to assert tag_name and previous_tag_name appear on the SAME generate-notes call, not just as two substrings present somewhere in the overall command log. --- .github/release.yml | 12 +++++++----- test/unit/release-selfhost-notes.test.ts | 5 +++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/release.yml b/.github/release.yml index b359808c91..31690ce9ea 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -7,11 +7,13 @@ # # `exclude.authors` matches the exact GitHub API login, which for a bot account includes the `[bot]` # suffix (confirmed against this repo's own PR history: dependabot's login is literally -# `dependabot[bot]`, not `dependabot`) -- excludes the two pure dependency-bump bots so routine version -# bumps don't clutter an operator-facing image changelog. Deliberately does NOT exclude -# `sentry[bot]` (Seer-authored fixes for real production errors, e.g. #3305, get a normal -# `gittensor:bug` label and are exactly the kind of change this changelog exists to surface) or -# `gittensory-orb[bot]` (the review engine's own rare, real content changes, e.g. #3397). +# `dependabot[bot]`, not `dependabot`) -- excludes `dependabot[bot]`/`renovate[bot]` (routine dependency +# version bumps) and `github-actions[bot]` (no PRs here currently, but automation-authored chore/release +# PRs are the same operator-facing noise if that ever changes) so none of the three clutter an +# operator-facing image changelog. Deliberately does NOT exclude `sentry[bot]` (Seer-authored fixes for +# real production errors, e.g. #3305, get a normal `gittensor:bug` label and are exactly the kind of +# change this changelog exists to surface) or `gittensory-orb[bot]` (the review engine's own rare, real +# content changes, e.g. #3397). changelog: exclude: authors: diff --git a/test/unit/release-selfhost-notes.test.ts b/test/unit/release-selfhost-notes.test.ts index ffaa373b62..fa38546173 100644 --- a/test/unit/release-selfhost-notes.test.ts +++ b/test/unit/release-selfhost-notes.test.ts @@ -143,6 +143,11 @@ describe('release-selfhost.yml "GitHub Release" step changelog generation', () = // The tag being released must never be diffed against itself. expect(r.calls).toContain("previous_tag_name=orb-v0.1.0"); expect(r.calls).not.toContain("previous_tag_name=orb-v0.2.0"); + // Lock the explicit range as ONE call, not just two substrings present somewhere in the log -- + // tag_name and previous_tag_name must be parameters of the same generate-notes invocation. + const apiCall = r.calls.split("\n").find((line) => line.includes("gh api")); + expect(apiCall).toContain("tag_name=orb-v0.2.0"); + expect(apiCall).toContain("previous_tag_name=orb-v0.1.0"); }); it("falls back to the plain notes with no changelog section when there is no prior orb-v tag", () => {