feat(release-branch-freeze): add code-freeze action for release branches#159
Merged
Conversation
loft-bot
reviewed
Jul 2, 2026
Piotr1215
added a commit
that referenced
this pull request
Jul 2, 2026
…and create path Findings from the PR review swarm on #159, all correctness or test-coverage gaps in the freeze action: - find_ruleset_id listed rulesets without --paginate. The REST API caps a page at 30 rulesets, so on a repo with more than one page the freeze ruleset could sit on a later page and be missed: freeze would then create a duplicate and unfreeze would report "nothing to unfreeze" while the branch stayed frozen. Paginate the list, as sibling gh-CLI actions (sticky-pr-comment) already do. - The create path parsed the POST response with jq '.id', which emits the literal "null" when the 2xx body has no id. [ -n "null" ] is true, so we wrote ruleset-id=null and exited 0, reporting a freeze that may not have applied. Use '.id // empty' and fail loudly, matching find_ruleset_id's own guard. - The unfreeze test asserted only the ::notice:: log line, never that the PUT carried enforcement=disabled, and no unfreeze test checked ruleset-id reached GITHUB_OUTPUT. The gh mock now records -f key=value fields so the test can assert both. - The error-propagation test only failed the initial GET (read side). Added a write-failure knob (GH_MOCK_FAIL_WRITE) so a test proves the PUT path surfaces errors, plus a no-id knob (GH_MOCK_POST_NO_ID) covering the new create guard. - Output description Id -> ID; README regenerated via make generate-docs.
sydorovdmytro
approved these changes
Jul 3, 2026
Piotr1215
added a commit
that referenced
this pull request
Jul 3, 2026
…and create path Findings from the PR review swarm on #159, all correctness or test-coverage gaps in the freeze action: - find_ruleset_id listed rulesets without --paginate. The REST API caps a page at 30 rulesets, so on a repo with more than one page the freeze ruleset could sit on a later page and be missed: freeze would then create a duplicate and unfreeze would report "nothing to unfreeze" while the branch stayed frozen. Paginate the list, as sibling gh-CLI actions (sticky-pr-comment) already do. - The create path parsed the POST response with jq '.id', which emits the literal "null" when the 2xx body has no id. [ -n "null" ] is true, so we wrote ruleset-id=null and exited 0, reporting a freeze that may not have applied. Use '.id // empty' and fail loudly, matching find_ruleset_id's own guard. - The unfreeze test asserted only the ::notice:: log line, never that the PUT carried enforcement=disabled, and no unfreeze test checked ruleset-id reached GITHUB_OUTPUT. The gh mock now records -f key=value fields so the test can assert both. - The error-propagation test only failed the initial GET (read side). Added a write-failure knob (GH_MOCK_FAIL_WRITE) so a test proves the PUT path surfaces errors, plus a no-id knob (GH_MOCK_POST_NO_ID) covering the new create guard. - Output description Id -> ID; README regenerated via make generate-docs.
affb5af to
a56e759
Compare
DEVOPS-1052. When a release branch is cut, the window between feature-complete and the stable tag must stay manager-only so late changes are deliberate and reviewed, then relax once the stable tag lands. There was no repeatable, automatable way to do this. Classic branch protection cannot express it: its push restriction gates direct pushes but not PR merges. A GitHub ruleset with the "Restrict updates" rule does gate merges (a PR merge is a ref update), so this action manages one reusable ruleset per repo. freeze upserts the ruleset onto the branch being released with a bypass team; unfreeze disables it. Only the branch in its release window is affected, so other release lines keep their normal rules. Kept repo-level so a repo-admin token manages it without org-admin scope. Verified end to end against a throwaway repo in vClusterLabs-Experiments: a non-bypass merge is blocked, a bypass merge succeeds via the PR merge API.
…and create path Findings from the PR review swarm on #159, all correctness or test-coverage gaps in the freeze action: - find_ruleset_id listed rulesets without --paginate. The REST API caps a page at 30 rulesets, so on a repo with more than one page the freeze ruleset could sit on a later page and be missed: freeze would then create a duplicate and unfreeze would report "nothing to unfreeze" while the branch stayed frozen. Paginate the list, as sibling gh-CLI actions (sticky-pr-comment) already do. - The create path parsed the POST response with jq '.id', which emits the literal "null" when the 2xx body has no id. [ -n "null" ] is true, so we wrote ruleset-id=null and exited 0, reporting a freeze that may not have applied. Use '.id // empty' and fail loudly, matching find_ruleset_id's own guard. - The unfreeze test asserted only the ::notice:: log line, never that the PUT carried enforcement=disabled, and no unfreeze test checked ruleset-id reached GITHUB_OUTPUT. The gh mock now records -f key=value fields so the test can assert both. - The error-propagation test only failed the initial GET (read side). Added a write-failure knob (GH_MOCK_FAIL_WRITE) so a test proves the PUT path surfaces errors, plus a no-id knob (GH_MOCK_POST_NO_ID) covering the new create guard. - Output description Id -> ID; README regenerated via make generate-docs.
a56e759 to
b5ab133
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a reusable
release-branch-freezecomposite action that applies or lifts a code freeze on a release branch by managing a GitHub repository ruleset.operation: freezeupserts one reusable ruleset per repo (default namerelease-branch-code-freeze) onto the branch being released, with theRestrict updatesrule and a bypass team. Only that team can merge into the branch.operation: unfreezedisables the ruleset, so the branch returns to its standing rules.enforcementinput supportsactive(default),evaluate(dry run, logs would-be blocks without blocking), anddisabled.Closes DEVOPS-1052.
Why this mechanism
Classic branch protection cannot express manager-only merges: its "restrict who can push" setting gates direct pushes but not the PR merge button, so any writer could still merge. A ruleset with the
Restrict updatesrule does gate merges, because a PR merge is a ref update. The org already runs on rulesets, so a per-branch freeze ruleset layers on top of the standing protection and removing it restores the exact prior state.Repo-level (not org-level) so a repo-admin token or GitHub App can manage it in CI without org-admin scope.
Integration test (vClusterLabs-Experiments)
The end-to-end ruleset behavior was validated against a throwaway repo,
vClusterLabs-Experiments/devops-1052-freeze-test, before writing the action:Bypass in that run used the admin repository role, since the experiments repo has no team; GitHub evaluates bypass actor types the same way at merge time, so the Team bypass this action uses behaves identically.
Tests
make test-release-branch-freezeruns the bats suite againstsrc/freeze.shwith a stubbedgh, covering input validation, freeze create and update (upsert), enforcement modes, custom ruleset name, unfreeze disable, unfreeze no-op, and error propagation.Follow-ups (separate PRs, not in this one)
vcluster-pro(branch patternv*.*, stable tagv*.*.0) andloft-enterprise(branch patternrelease-*), each wiring freeze on branch create and unfreeze on the stable tag. The two repos have different branch naming, so the callers differ; the action itself is repo-agnostic.CODE_FREEZE_TOKENsecret (Administration: read and write on the target repos).Eng-Tech-Leads(id 16898535).release-branch-freeze/v1after merge.