ci: merge freezes as owned, expiring GitHub issues
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:orexpires: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-okto 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 checkdecides, 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.ymlshows amerge freezecheck on pull requests into main.pull_request_targetreads 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'spull_request_target, and it checks out only main's checker, never the pull request.docs/ci/merge-freeze.mdis the how-to.
Should freezes exist at all?
Mostly no. Per-PR risk is now the job of the risk tiers (manaflow-ai/cmux#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-onlyfreeze-ok(including a later removal), theowner:andbranch:lines, closed or unlabeled issues, and an unreadable list returning 2. The test is wired intoci-guards.ymland registered intests/test-execution.toml.- Live, read-only against manaflow-ai/cmux:
listshows no open freezes,check --pr 21690exits 0, andexpire --dry-runcloses 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 5ff3070b765 (cmux-review subagent), and 2ef1e133f7c fixed those nits. coord-2's independent review at 2ef1e133f7c said fix first: the test registry entry was missing, and anyone could label a freeze. 21a1da5c5d7 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 21a1da5c5d7, 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