fix(scripts/release): promote stable on immutable releases (#30745)

## Problem

`./scripts/release_promote_stable.sh v2.37.4` failed today with:

```
edit release failed: PATCH https://api.github.com/repos/coder/coder/releases/403906567: 404 Not Found
```

Releases on this repo are immutable (`gh release view v2.37.4 --json
isImmutable` → `true`). `promoteVersionToStable` cloned the entire
`RepositoryRelease` into `EditRelease`, so the PATCH carried `tag_name`,
`target_commitish` and the assets. GitHub rejects that on an immutable
release even when the values are unchanged, and answers 404 rather than
422. The same edit with only `body`, `prerelease` and `make_latest`
succeeds (that's how v2.37.4 was promoted by hand).

## Change

- Build a minimal `RepositoryRelease{Body, Prerelease, MakeLatest}` for
the edit.
- Drop the `Draft: false` assignment (published releases are never
drafts, and `draft` is not an editable field on immutable releases).
- Dry-run now logs the body diff instead of a diff of the whole object.
- Remove the now-unused `cloneRelease` helper.

Verified with `go test ./scripts/release/` and a `promote --dry-run`
against v2.36.7.

Every release published since immutable releases were enabled is
affected, so without this the command cannot promote anything. Related:
the release-process review notes that this step is also missing from the
runbook and should become a workflow job on every `.0` mainline release.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Denis Afonso
2026-10-14 16:42:56 +01:00
committed by GitHub
co-authored by Claude
parent 2b9fad8bba
commit 200cffcfd8
+12 -14
View File
@@ -236,33 +236,31 @@ func (r *releaseCommand) promoteVersionToStable(ctx context.Context, inv *serpen
logger.Info(ctx, "promoting selected version to stable")
// Update the release to latest.
updatedNewStable := cloneRelease(newStable)
// Update the release to latest. Only send the fields we change:
// releases on this repo are immutable, and GitHub rejects an edit
// that carries the tag, target commitish or assets of an immutable
// release (it answers 404 Not Found), even when those values are
// unchanged. Body, prerelease and make_latest remain editable.
updatedBody := removeMainlineBlurb(newStable.GetBody())
updatedBody = addStableSince(time.Now().UTC(), updatedBody)
updatedNewStable.Body = github.String(updatedBody)
updatedNewStable.MakeLatest = github.String("true")
updatedNewStable.Prerelease = github.Bool(false)
updatedNewStable.Draft = github.Bool(false)
edit := &github.RepositoryRelease{
Body: github.String(updatedBody),
MakeLatest: github.String("true"),
Prerelease: github.Bool(false),
}
if !r.dryRun {
_, _, err = client.Repositories.EditRelease(ctx, owner, repo, newStable.GetID(), updatedNewStable)
_, _, err = client.Repositories.EditRelease(ctx, owner, repo, newStable.GetID(), edit)
if err != nil {
return xerrors.Errorf("edit release failed: %w", err)
}
logger.Info(ctx, "selected version promoted to stable", slog.F("url", newStable.GetHTMLURL()))
} else {
logger.Info(ctx, "dry-run: release not updated", slog.F("uncommitted_changes", cmp.Diff(newStable, updatedNewStable)))
logger.Info(ctx, "dry-run: release not updated", slog.F("body_diff", cmp.Diff(newStable.GetBody(), updatedBody)))
}
return nil
}
func cloneRelease(r *github.RepositoryRelease) *github.RepositoryRelease {
rr := *r
return &rr
}
// addStableSince adds a stable since note to the release body.
//
// Example: