Skip to content

Audit: Merge queue reasonability

What we check

Repositories that enable a GitHub merge queue on their default branch must configure it so entries are processed serially and merged individually:

  • max_entries_to_build: 1 — no speculative stacking.
  • min_entries_to_merge: 1 — merge each green entry immediately.

Repositories without a merge queue are out of scope; whether to adopt two-stage CI at all is a per-project decision.

The check reads the effective rules for the default branch via GET /repos/shakenfist/<repo>/rules/branches/<branch> and validates the parameters of any merge_queue rule found.

Why these values

Both were learned the hard way on shakenfist/shakenfist in August 2026.

Speculative stacking multiplies failures. With max_entries_to_build above 1, entry N+1 builds on a temporary branch containing entry N's changes, so when any entry ahead fails the entries behind it are ejected and rebuilt on new SHAs. On a CI cluster whose dominant failure mode is load this is doubly wrong: the stacked builds waste runs (we observed single PRs rebuilt five times in one day) and the extra concurrent merge groups add the very load that causes the failures. A serialized queue never starts CI for an entry until the one ahead has merged or ejected.

Batched merging is pure latency. The queue always builds one merge group and runs CI once per entry no matter how merges are batched — min_entries_to_merge only controls how many green entries land in a single default-branch update. Raising it makes the queue idle for up to min_entries_to_merge_wait_minutes hoping more PRs arrive, which on a mostly-single-developer project delays every merge and saves nothing. At 1 that wait timer never engages.

Other parameters (grouping_strategy, merge_method, max_entries_to_merge, check_response_timeout_minutes) are conventions rather than correctness issues and are not enforced; the fleet reference is shakenfist/shakenfist's "Develop branch" ruleset: ALLGREEN, MERGE, 5, and 360 respectively.

Template

No template — this is a one-time ruleset configuration change.

To inspect the current configuration:

gh api repos/shakenfist/<repo>/rules/branches/develop \
    --jq '.[] | select(.type == "merge_queue") | .parameters'

To fix a non-compliant ruleset, fetch it, rewrite the two parameters, and PUT it back, preserving everything else including bypass_actors:

gh api repos/shakenfist/<repo>/rulesets --jq \
    '.[] | select(.target == "branch") | {id, name}'
gh api repos/shakenfist/<repo>/rulesets/<id> | jq \
    '{name, target, enforcement, conditions,
      bypass_actors: (.bypass_actors // []),
      rules: (.rules | map(
        if .type == "merge_queue"
        then .parameters.max_entries_to_build = 1
           | .parameters.min_entries_to_merge = 1
        else . end))}' > /tmp/ruleset.json
gh api -X PUT repos/shakenfist/<repo>/rulesets/<id> \
    --input /tmp/ruleset.json

Afterwards, trigger the repository's export-repo-config workflow so the change is captured in .github/exported-config/.

Projects

Per-project compliance for this criterion is regenerated every morning by the consistency audit: see the compliance page.

📝 Report an issue with this page