Skip to content

ci: merge freezes as owned, expiring GitHub issues - #21706

Merged
teamleaderleo merged 3 commits into
mainfrom
claude/merge-freeze-issues
Oct 18, 2026
Merged

teamleaderleo merged 3 commits into
mainfrom
claude/merge-freeze-issues

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Merge freezes become GitHub issues that anyone can see, that have an owner, and that expire on their own, instead of FREEZE: lines in ~/agent-mailbox/WINDOW on cmux-lawrence.

That file broke in three ways. gh-merge-green read it over ssh, so a host without ssh to cmux-lawrence (Huge Blue) couldn't merge into feat-cmux-next at all. Every token in it had lapsed by 2026-10-10, but the tools kept refusing on it. And nobody but its readers could see it was stale.

How a freeze works now

Open an issue and have an admin or maintainer add the merge-freeze label. The title is the reason, and the body says:

paths: cmux-tui/crates/acpmux/ cmux-tui/spec/
expires: 2026-10-20T18:00Z
owner: @someone          (optional; the author by default)
branch: feat-cmux-next   (optional)
  • Only a maintainer turns a freeze on: the label must come from an admin or maintainer, after the body's last edit. Editing the body afterwards, to widen paths: say, turns it off until a maintainer labels it again.
  • A freeze lasts at most 72 hours from when its issue opened. A missing or unparseable paths: or expires: blocks nothing and is reported as malformed, so a typo can't freeze the repository.
  • The owner lets a pull request through by adding freeze-ok to it. The checker reads who added the label from the timeline, so a label from anyone else doesn't count.
  • scripts/ci/merge_freeze.py check decides, using only the GitHub API, so it works the same from any host. It exits 0 for clear, 1 for frozen, and 2 when GitHub can't be read, which counts as frozen.
  • .github/workflows/merge-freeze.yml shows a merge freeze check on pull requests into main. pull_request_target reads the workflow from the base branch, so feat-cmux-next pull requests get the check once the same file lands there, in a one-file follow-up after this merges. Every hour it closes lapsed freezes with a comment, so the open issues are exactly the live freezes. It's pull_request_target, and it checks out only main's checker, never the pull request.
  • docs/ci/merge-freeze.md is the how-to.

Should freezes exist at all?

Mostly no. Per-PR risk is now the job of the risk tiers (#21691), and the batch queue validates stacks. What's left is coordination: a release cut, or one lane reworking an area others would collide with. That justifies a small, visible, expiring freeze, not a standing one.

Not in this pull request: switching gh-merge-green

gh-merge-green still reads the WINDOW file. Replacing that block with merge_freeze.py check, and dropping the RELEASE mail it sends to cmux-lawrence, is a separate change. It needs explicit approval, because it edits the merge gate's freeze logic. ~/Projects/briefs/huge-blue-merge-ready.sh has its own hard-coded WINDOW path list and gets the same change. The old file on cmux-lawrence is left untouched.

Testing

  • python3 tests/test_merge_freeze.py: 13 tests pass, including that labels from triage, write, outside users or nobody don't count, and that an edit after the label turns the freeze off. They cover prefix matching including siblings, expiry, the 72-hour cap, malformed freezes, owner-only freeze-ok (including a later removal), the owner: and branch: lines, closed or unlabeled issues, and an unreadable list returning 2. The test is wired into ci-guards.yml and registered in tests/test-execution.toml.
  • Live, read-only against manaflow-ai/cmux: list shows no open freezes, check --pr 21690 exits 0, and expire --dry-run closes nothing.
  • bash scripts/ci/guards-local.sh: 108 pass and 3 fail. On main without this change, 105 pass and 5 fail, and no failure is new.
  • Not run: the workflow itself, which runs from main after merge.

Review: SHIP with nits at 5ff3070 (cmux-review subagent), and 2ef1e13 fixed those nits. coord-2's independent review at 2ef1e13 said fix first: the test registry entry was missing, and anyone could label a freeze. 21a1da5 fixes both. Left as-is: one freeze-ok can't satisfy two freezes with different owners on the same PR, and approval isn't reset by a later push.
Runtime test: at 21a1da5, merge_freeze.py list and check --pr 21706 against manaflow-ai/cmux (exit 0), plus the new helpers live: repo_role (admin for teamleaderleo, read for an outsider), last_edited on issue #21611, and last_labeler on #21334's events.

Changelog

none

Proof

Not UI: merge tooling and docs.

🤖 Generated with Claude Code

A freeze is an open merge-freeze issue naming paths, an owner and an expiry
(at most 72 hours). merge_freeze.py checks a pull request against the open
freezes through the GitHub API from any host; only the owner's freeze-ok
label lets one through, and an unreadable state counts as frozen.
merge-freeze.yml shows a 'merge freeze' check on pull requests into main
and feat-cmux-next and closes lapsed freezes hourly.

This replaces the FREEZE lines in cmux-lawrence's WINDOW file, whose tokens
had all lapsed by 2026-10-10 while tools still refused on them. Switching
gh-merge-green to this checker is a separate change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 23 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 2c0ccb4b-1092-4855-ba86-dbb85e8bf33d
📥 Commits

Reviewing files that changed from the base of the PR and between 2e75a00 and 21a1da5.

📒 Files selected for processing: 6
  • .github/workflows/ci-guards.yml
  • .github/workflows/merge-freeze.yml
  • docs/ci/merge-freeze.md
  • scripts/ci/merge_freeze.py
  • tests/test-execution.toml
  • tests/test_merge_freeze.py
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

…lizes paths

From review: grade both ends of a rename, exit 2 when the files API lists
fewer files than the pull request changed (its 3000-file cap), read './x/',
'/x/' and 'x/,' as 'x/', and report globs as malformed instead of matching
nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 18, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Passes: CI passes on 21a1da5c5d.

CI passes on 21a1da5c5d (run 39138736096 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

CI fast guards passes on 21a1da5c5d (https://github.com/manaflow-ai/cmux/actions/runs/39138736096). This covers only the fast guards, not CI: CI's result is the ci-status check, and the CI failure attribution comment names any failing test.

From coord-2's review: any triager could label an issue merge-freeze, and
its author could then widen paths: for 72 hours. A freeze is now active
only when an admin or maintainer added the label, after the body's last
edit; an edit afterwards turns it off until a maintainer labels it again.
Also registers tests/test_merge_freeze.py in tests/test-execution.toml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 18, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@teamleaderleo
teamleaderleo merged commit d95c1f3 into main Oct 18, 2026
57 checks passed
@teamleaderleo
teamleaderleo deleted the claude/merge-freeze-issues branch October 18, 2026 15:41
@github-actions

Copy link
Copy Markdown
Contributor

Merge receipt for 21a1da5c5d: every check was green at merge (9 verified; 15 skipped by policy). Full suite runs on main after merge.

teamleaderleo added a commit that referenced this pull request Oct 19, 2026
…ive freezes (#21938)

* test(merge-freeze): unexpected API shapes exit 2, and old inactive freezes close

Fails on main: a null created_at escapes as exit 1, and lapsed_at doesn't
exist yet (expire never closes unauthorised or malformed freezes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(merge-freeze): unknown API shapes read as unknown; close old inactive freezes

From coord-2's review of #21706: a TypeError or AttributeError from an
unexpected GitHub answer now exits 2 (unknown), not 1, and expire closes a
malformed or unauthorised freeze issue once 72 hours have passed since it
opened instead of leaving it open forever.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(merge-freeze): list and expire report read errors as 2; clearer close comment

From review of 694c4f6: the error message read args.pr, which list and
expire don't have, so their read errors escaped as exit 1. And a freeze
edited after its label may have been active before, so the close comment
says it isn't active rather than that it never was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant