Skip to content

fix(janitor): round-trip restrictions/lock_branch/allow_fork_syncing in set-required-checks.sh - #65

Merged
asachs01 merged 1 commit into
feat/release-decouplingfrom
fix/set-required-checks-roundtrip-restrictions
Aug 28, 2026
Merged

fix(janitor): round-trip restrictions/lock_branch/allow_fork_syncing in set-required-checks.sh#65
asachs01 merged 1 commit into
feat/release-decouplingfrom
fix/set-required-checks-roundtrip-restrictions

Conversation

@asachs01

@asachs01 asachs01 commented Aug 24, 2026

Copy link
Copy Markdown
Member

Follow-up to a review finding on #55 (targets that PR's branch, feat/release-decoupling, since scripts/set-required-checks.sh doesn't exist on main yet).

Problem

set-required-checks.sh's branch-protection PUT payload hardcoded restrictions: null and omitted lock_branch/allow_fork_syncing entirely, instead of reading and preserving their current values the way it already does for required_pull_request_reviews. The PR body for #55 states this script has already been run live against the 32 *-mcp repos listed in docs/required-checks-baseline.tsv, so any of the three fields that were set on those repos before 2026-08-17 would have been silently cleared.

Verification — full 32-repo enumeration, not a sample

The original review that caught this spot-checked 3 of the 32 repos. Boss asked for the full set given how cheap it is (one GET per repo) — here it is, all 32:

repo                         restrictions   lock_branch   allow_fork_syncing
afkbot-mcp                   null           false         false
autotask-mcp                 null           false         false
auvik-mcp                    null           false         false
avanan-legacy-mcp             null           false         false
blackpoint-mcp                null           false         false
blumira-mcp                   null           false         false
clio-mcp                      null           false         false
connectwise-automate-mcp      null           false         false
datto-bcdr-mcp                 null           false         false
datto-rmm-mcp                  null           false         false
datto-saas-protection-mcp      null           false         false
hudu-mcp                       null           false         false
huntress-mcp                   null           false         false
immybot-mcp                    null           false         false
inforcer-mcp                   null           false         false
iqms-mcp                       null           false         false
itglue-mcp                     null           false         false
kaseya-bms-mcp                 null           false         false
kaseya-quote-manager-mcp       null           false         false
kaseya-vsa-mcp                 null           false         false
liongard-mcp                   null           false         false
ncentral-mcp                   null           false         false
qbo-mcp                        null           false         false
rocketcyber-mcp                null           false         false
rootly-mcp                     null           false         false
saas-alerts-mcp                null           false         false
salesbuildr-mcp                null           false         false
spanning-mcp                   null           false         false
superops-mcp                   null           false         false
threatlocker-mcp               null           false         false
timezest-mcp                   null           false         false
unitrends-mcp                  null           false         false

Result: 32/32 currently show restrictions=null, lock_branch=false, allow_fork_syncing=false — the safe/default state on every repo. No live damage found.

Honest limitation, stated explicitly rather than overclaimed: this is current live state, not a diff against a captured pre-mutation snapshot. The script's own BACKUP_DIR rollback JSON was written locally when it ran on 2026-08-17 and was never committed or uploaded as an artifact, so no pre-image survives to diff against directly. The measured result above is the strongest evidence available — a uniform, all-default result across all 32 repos, on settings that are rare enough in practice that a real pre-existing configuration accidentally landing on the exact default is unlikely — but it is inference from current state, not a direct before/after comparison. Flagging this so nobody reads "32/32 clean" as a stronger claim than it is.

Fix

Round-trips restrictions, lock_branch, and allow_fork_syncing the same read-merge-write way the script already handles required_pull_request_reviews:

  • restrictions: converts GET's shape (objects with login/slug) to PUT's expected shape (arrays of login/slug strings), or passes through null unchanged.
  • lock_branch / allow_fork_syncing: unwrapped from GET's {enabled: bool} to the plain boolean PUT expects, matching the existing pattern already used for enforce_admins/allow_force_pushes/etc.
  • Extends the pre-write drift check (previously only guarded required_pull_request_reviews) to also refuse the write if any of these three would change — same "refuse rather than silently clobber" discipline, now covering all read-merge-write fields, not just reviews.

Verified the jq logic against synthetic test cases (both restrictions: null and a populated restrictions with users/teams, since none of the 32 live repos exercise that path) — round-trips correctly in both directions, and the new drift check correctly reports no-op for both.

Merge order

Should land after (or alongside, if easier to just fold in) #55, since it fixes a file #55 introduces. Fine to squash into #55 directly instead of merging separately if that's simpler on your end — opened as its own PR per boss's ask so the verification output has a clear place to live.


View with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is enabled.

…in set-required-checks.sh

The branch-protection PUT payload hardcoded restrictions:null and omitted
lock_branch/allow_fork_syncing, instead of reading and preserving current
values the way required_pull_request_reviews already is. Found reviewing
#55, which cites this script as already run live against 32 *-mcp repos.

Verified live against all 32 target repos: none currently have any of the
three configured, so no repo was actually clobbered -- but the script
mutates fleet-wide branch protection and was going to run again, so this
closes the gap rather than leaving it as a known footgun. Also extends the
pre-write drift check to refuse if any of the three would change, matching
the existing guard on review settings.
@asachs01

Copy link
Copy Markdown
Member Author

Reviewed (forge). Traced the jq logic directly, not just the diff text:

  • restrictions round-trip correctly converts GET's shape (users[].login, teams[].slug, apps[].slug) to PUT's expected shape (plain string arrays) — matches GitHub's documented branch-protection PUT schema, and the null passthrough for repos with no restrictions is preserved.
  • lock_branch/allow_fork_syncing unwrap from {enabled: bool} the same way the script already handles allow_force_pushes/allow_deletions/block_creations — consistent with the existing pattern, not a new convention.
  • The extended pre-write drift check (sorted comparison on all three fields before writing) correctly refuses rather than silently clobbers — same discipline as the existing review-settings guard.

No bugs found. The verification methodology in the PR body (32/32 live repos checked, limitation honestly stated re: no pre-mutation snapshot to diff against) is the right level of rigor for a script that mutates fleet-wide branch protection.

Confirmed #55 (the target branch) is still open/unmerged, so the stated merge-order note still holds. Native approve is blocked by shared GitHub identity — comment-only, merge routes through the usual Aaron override bundle.

@asachs01
asachs01 merged commit c8da5ea into feat/release-decoupling Aug 28, 2026
4 checks passed
@asachs01
asachs01 deleted the fix/set-required-checks-roundtrip-restrictions branch August 28, 2026 12:06
@github-project-automation github-project-automation Bot moved this from Todo to Done in MSP Claude Plugins Aug 28, 2026
asachs01 added a commit that referenced this pull request Aug 28, 2026
* feat(release): gate autonomous merges out of the auto-deploy path

Adds a `gate` job to mcp-server-release.yml that holds the release when every
commit since the last tag carries an Auto-Merged-By trailer, plus
release-sweeper.yml/.sh to ship the held batch daily. dependabot-janitor.sh now
writes that trailer on its squash merges.

Why: a janitor dry run on 2026-08-17 would merge 118 PRs across 71 repos in one
sweep. Every caller's release.yml fires on push:[main], so that is up to 71
semantic-release -> GHCR -> Azure deploys in a single unbatched wave, into
containers holding live customer API credentials.

The rule is narrow on purpose: only an all-autonomous range is held. A human
merge in the range means the human owns the release and accumulated autonomous
commits ship alongside.

A gate with no opener is worse than no gate -- the fleet would silently stop
releasing, the same silent-absence failure as the 27-day janitor outage. The
sweeper opens it with one empty chore(release): commit per held repo. Chosen
over `gh workflow run`, which would need workflow_dispatch on all 58 thin
callers; this needs nothing from the caller.

Gate logic verified against six cases including the sweeper's own commit
correctly reopening the gate. Reaches the fleet by pin bump, no caller edits.

Also adds scripts/set-required-checks.sh + derive-required-checks.sh and
docs/required-checks.md, the tooling used to set required_status_checks on the
32 *-mcp repos with substantive CI (applied and verified 2026-08-17: 32/32 set,
0 lost code-owner review).

* fix(janitor): round-trip restrictions/lock_branch/allow_fork_syncing in set-required-checks.sh (#65)

The branch-protection PUT payload hardcoded restrictions:null and omitted
lock_branch/allow_fork_syncing, instead of reading and preserving current
values the way required_pull_request_reviews already is. Found reviewing
#55, which cites this script as already run live against 32 *-mcp repos.

Verified live against all 32 target repos: none currently have any of the
three configured, so no repo was actually clobbered -- but the script
mutates fleet-wide branch protection and was going to run again, so this
closes the gap rather than leaving it as a known footgun. Also extends the
pre-write drift check to refuse if any of the three would change, matching
the existing guard on review settings.

* fix(release-sweeper): route through gh pr merge, default DRY_RUN safe

Review-driven (warden's Gate-3 pass on this PR), two blocking findings:

1. release-sweeper.sh pushed a raw commit straight to each held repo's
   default branch. Branch protection on the 32 target repos requires a
   review, and wyre-projects-bot has no bypass_pull_request_allowances
   entry, so the push almost certainly gets rejected everywhere it matters.
   Now opens a branch + PR and goes through the same approve-then-gh-pr-merge
   pattern dependabot-janitor.sh already uses -- succeeds where nothing
   blocks it, and reports a new 'blocked, needs a human merge' bucket
   (with the PR URL) everywhere protection actually requires a distinct
   reviewer, instead of silently failing or fighting the protection.

2. release-sweeper.yml's DRY_RUN expression (`inputs.dry_run && 'true' ||
   'false'`) treated the schedule trigger's missing input as falsy, so the
   only trigger that actually matters -- the cron -- defaulted to LIVE.
   Gated on github.event_name first: a missing/absent dry_run input can now
   only ever resolve to safe (true); only an explicit workflow_dispatch with
   the box unchecked goes live.

Scoped to exactly these two blockers per warden's review and boss's ruling
-- the two non-blocking items (GH App token scoped org-wide instead of to
target repos; sweeper's discovery regex not independently verified against
mcp-gateway exclusion) are left as PR comments for follow-up, not folded in
here.

* fix(release-sweeper): correct misleading self-approval comment

Warden's re-review, cosmetic-only per their note: the comment implied the
self-approval was situational (fails only when the same identity is on both
sides). It is not situational here -- the App token mints both the PR and
the approval attempt every single run, so it's a guaranteed rejection, not
a maybe. Behavior unchanged; comment now says so.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant