Skip to content

ci: main cannot enforce required status checks while owned by a user account #192

Open

Description

@TMHSDigital

Goal

Block a red PR from being merged into main.

The ruleset now on main does not do that. main-integrity (ruleset 23797003, no bypass actors) enforces only:

  • deletion
  • non_fast_forward
  • required_linear_history

Force-push to main is now refused (verified, GH013: ... - Cannot force-push to this branch). But required status checks are not enforced, and a pull request is not required. A PR whose smoke jobs are red can still be merged, and anything holding a repo-scoped token can still fast-forward main directly. Merge-on-green remains convention, not enforcement.

Why required status checks cannot be added today

Required status checks block direct pushes to the branch for any actor without a bypass, not just PR merges. release.yml pushes the version-bump commit to main as github-actions[bot] via secrets.GITHUB_TOKEN. Adding the rule therefore breaks the release.

On a user-owned repository there is no valid bypass actor for GitHub Actions. Attempting to add one is rejected at ruleset creation:

POST /repos/TMHSDigital/Blender-Developer-Tools/rulesets
{"actor_id": 15368, "actor_type": "Integration", "bypass_mode": "always"}

422 Validation Failed
"Actor GitHub Actions integration must be part of the ruleset source or owner organization"

Probe results

Measured on a throwaway branch (protection-probe), with a workflow pushing as github-actions[bot] using GITHUB_TOKEN. Branch and probe rulesets have been torn down.

Configuration Owner direct push github-actions[bot] push
Ruleset: PR + required checks, no bypass REFUSED GH013 REFUSED GH013
Ruleset: PR + required checks, bypass RepositoryRole:Write ALLOWED (Bypassed rule violations) REFUSED GH013
Ruleset: required checks only (no PR rule), no bypass — REFUSED GH013
Classic protection, enforce_admins: false, restrictions: null allowed (admin) REFUSED GH006

Classic-protection probe, verbatim:

remote: error: GH006: Protected branch update failed for refs/heads/protection-probe.
remote:
remote: - Changes must be made through a pull request.
remote:
remote: - Required status check "Validate content counts" is expected.
 ! [remote rejected] HEAD -> protection-probe (protected branch hook declined)

Role-based bypass is backwards for this repo: RepositoryRole:Write covers the owner (admin inherits write) and never covers the bot.

Why an owner PAT is not a fix

Routing the bump push through an owner PAT only works if the owner can bypass — i.e. enforce_admins: false on classic protection, or a role bypass on a ruleset. Either one also permits admin merges of red PRs. This repo's agent sessions merge as the owner, so that is precisely the hole the protection exists to close. A PAT converts the problem into a bypass the day-to-day merge path already uses.

Two paths

No recommendation; both are structural.

1. Transfer the repository to a GitHub organization.
In an org, Integration: github-actions is a legal bypass actor. Required status checks and a PR requirement can then coexist with release.yml untouched: the bot bypasses the rules for its bump push while human merges stay gated. Cost: the repository changes owner.

2. Restructure release.yml so it never pushes to main.
Derive VERSION from the git tag and generate changelog content at build time rather than committing it. With no push to main, no bypass actor is needed and full protection applies to every actor including the owner.

Dependency to resolve first: scripts/site/build_site.py:188 reads the committed CHANGELOG.md (changelog_path = repo_root / "CHANGELOG.md"). Path 2 requires that source to move to generated-at-build-time content, or the landing page loses its changelog section. release-doc-sync@v1 also rewrites CLAUDE.md's **Version:** line and ROADMAP.md's **Current:** line in-tree, and .cursor-plugin/plugin.json carries a committed version field validated by CI — all four are commit-to-main couplings that path 2 has to unwind.

Until one of those lands

Merge-on-green is convention, not enforcement. main is protected against history rewrites and branch deletion only.

Activity

  1. TMHSDigital commented on Sep 23, 2026

    @TMHSDigital
    OwnerAuthor

    One dependency listed for path 2 is not real

    The issue says path 2 must first move scripts/site/build_site.py's changelog source, "or the landing page loses its changelog section". The landing page has no changelog section.

    • build_site.py does parse CHANGELOG.md (parse_changelog, now around line 225) and passes changelog / has_changelog into the template context.
    • scripts/site/template.html.j2 never uses either: grep -c changelog scripts/site/template.html.j2 → 0. The live page (v0.79.6) shows no changelog.

    So the committed CHANGELOG.md is dead input to the site. Path 2 can drop parse_changelog and the CHANGELOG.md entry in pages.yml's paths: without changing what the site shows. The other three in-tree couplings still stand: CLAUDE.md **Version:**, ROADMAP.md **Current:** and plugin.json version.

    No opinion on path 1 vs 2. This only removes one item from path 2's cost.

  2. TMHSDigital commented on Oct 3, 2026

    @TMHSDigital
    OwnerAuthor

    Triage (no change made; this needs an owner decision).

    Third path, untested: rulesets accept DeployKey as a bypass actor type, and unlike Integration: github-actions that is not limited to org-owned repos as far as the docs say. release.yml would check out and push the bump commit over SSH with a write deploy key (secret RELEASE_DEPLOY_KEY), the ruleset would list that key as the only bypass, and no role bypass would exist, so the owner's merges of red PRs stay blocked. I did not probe it: adding a deploy key to the repo was blocked in my session, so it needs you to try (or to authorize it).

    What enforcing would cost in practice:

    • Required checks and a PR rule block every direct push to main, including the per-piece git push origin main showcase workflow and any agent that commits to main. Those would have to move to PRs.
    • Only checks that always run can be required. Blender Smoke now skips docs-only PRs (paths-ignore, ci: Blender Smoke runs on docs-only PRs and downloads Blender uncached and unverified (no sha256) #294), so a required smoke check would hang those PRs. Require the Validate jobs only; smoke stays enforced by the release gate.

    What already narrows the hole: since #312 a red main cannot cut a release (gate job: Validate on the pushed SHA plus all checks on the merged PR). Red merges are still possible; red releases are not.

    Recommendation: leave this open until you decide whether you are willing to give up direct pushes to main. If yes, try the deploy-key route on a throwaway branch first; if no, an org transfer is the only route that keeps both.

  3. TMHSDigital commented on Oct 4, 2026

    @TMHSDigital
    OwnerAuthor

    Status after the #319–#344 pass (no protection change made; still an owner decision):

    What now narrows the hole further

    Unchanged
    A red commit can still land on main. It just cannot be released. Merge-on-green for landing remains convention. The two paths in the issue body (transfer to an org, or release.yml never pushing to main) and the untested deploy-key bypass are still the only ways to enforce it.

    One new coupling for path 2: release.yml now also force-pushes the bot-built plugin-dist branch (#340). That branch is not protected and needs no bypass, so it does not affect either path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    cigithub_actionsPull requests that update GitHub Actions code

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions