ci: add main branch protection ruleset and docs #405 - #529
Conversation
Encode required CI checks, review, and no-force-push rules for main, and document how maintainers apply the ruleset. Closes Stellar-Ecosystem#405.
|
@xtep103 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
📝 WalkthroughWalkthroughThe pull request adds an active ChangesMain branch protection
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Maintainer
participant apply-main-branch-protection.sh
participant GitHub API
Maintainer->>apply-main-branch-protection.sh: Run the protection command
apply-main-branch-protection.sh->>GitHub API: Find Protect main ruleset
GitHub API-->>apply-main-branch-protection.sh: Return ruleset status
apply-main-branch-protection.sh->>GitHub API: Update or create ruleset
apply-main-branch-protection.sh->>GitHub API: Verify active main branch rules
GitHub API-->>apply-main-branch-protection.sh: Return checks and review settings
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/apply-main-branch-protection.sh`:
- Around line 21-25: Update the ruleset lookup assigned to EXISTING_ID to
request all pages from the repos/${OWNER_REPO}/rulesets API, ensuring matches
beyond the first page are found. Remove the error suppression and || true so gh
API lookup failures propagate and stop the script instead of falling through to
creation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d9059d11-5ffa-4068-92a3-51d9ea074862
📒 Files selected for processing (3)
.github/rulesets/main-protection.jsonCONTRIBUTING.mdscripts/apply-main-branch-protection.sh
| EXISTING_ID="$( | ||
| gh api "repos/${OWNER_REPO}/rulesets" --jq \ | ||
| ".[] | select(.name == \"${RULESET_NAME}\") | .id" \ | ||
| 2>/dev/null || true | ||
| )" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
OWNER_REPO="${OWNER_REPO:-Stellar-Ecosystem/lodestar}"
gh api --paginate "repos/${OWNER_REPO}/rulesets?per_page=100" --jq \
'.[] | select(.name == "Protect main") | .id'Repository: Stellar-Ecosystem/lodestar
Length of output: 164
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "Tracked file:"
git ls-files | grep -Fx 'scripts/apply-main-branch-protection.sh' || true
if [ -f scripts/apply-main-branch-protection.sh ]; then
echo "--- file outline/stats ---"
wc -l scripts/apply-main-branch-protection.sh
echo "--- relevant script section ---"
cat -n scripts/apply-main-branch-protection.sh | sed -n '1,140p'
echo "--- gh docs/options hints (local gh only) ---"
gh help api 2>/dev/null | sed -n '1,120p' || true
gh api -h 2>/dev/null | sed -n '1,120p' || true
fi
echo "--- repository ruleset count/page metadata ---"
OWNER_REPO="${OWNER_REPO:-Stellar-Ecosystem/lodestar}"
python3 - <<'PY' "$OWNER_REPO"
import subprocess, sys, json
repo=sys.argv[1]
for path in ["repos/%s/rulesets?per_page=2" % repo, "repos/%s/rulesets?per_page=30" % repo, "repos/%s/rulesets?per_page=100" % repo]:
out=subprocess.run(["gh","api",path], capture_output=True, text=True)
print("##", path)
print("status:", out.returncode)
print("headers X-Complete:", out.headers.get("X-GitHub-Media-Type") or out.headers.get("", ""), out.headers.get("Link") or "")
data=json.loads(out.stdout) if out.returncode==0 else None
if data is None:
print("json parse failed")
continue
print("count:", len(data), "names:", [d.get("name") for d in data])
PYRepository: Stellar-Ecosystem/lodestar
Length of output: 14912
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
OWNER_REPO="${OWNER_REPO:-Stellar-Ecosystem/lodestar}"
for per_page in 1 2 30 31 100; do
echo "## rulesets?per_page=${per_page}"
gh api "repos/${OWNER_REPO}/rulesets?per_page=${per_page}" \
--include --jq \
'{total_count: length, page_count: (.headers | keys | join(",")), first_name: .body[0].name, first_id: .body[0].id}' \
2>/tmp/coderabbit-${per_page}.stderr || true
if [ -s /tmp/coderabbit-${per_page}.stderr ]; then
sed -n '1,80p' /tmp/coderabbit-${per_page}.stderr
fi
done
echo "--- all pages names ---"
gh api --paginate "repos/${OWNER_REPO}/rulesets?per_page=2" --jq '.[] | "\(.id)=\(.name)"' | \
sort -n -t= -k1Repository: Stellar-Ecosystem/lodestar
Length of output: 7102
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
OWNER_REPO="${OWNER_REPO:-Stellar-Ecosystem/lodestar}"
echo "--- rulesets list full response counts ---"
python3 - <<'PY' "$OWNER_REPO"
import subprocess, json, sys
repo=sys.argv[1]
resp=subprocess.run(["gh","api","repos/"+repo+"/rulesets?per_page=2"], text=True,capture_output=True)
data=json.loads(resp.stdout) if resp.returncode==0 else []
print("status", resp.returncode)
print("full length", len(data))
for i, item in enumerate(data[:5], 1):
print(i, item.get("id"), item.get("name"))
PY
echo "--- one page with include headers ---"
gh api repos/${OWNER_REPO}/rulesets?per_page=1 --include --jq '.body' 2>&1 | sed -n '1,120p'
echo "--- jq predicate with include output ---"
gh api repos/${OWNER_REPO}/rulesets?per_page=1 --include --jq '
.body as $body |
.headers["Content-Type"] as $ct |
{headers_len: (.headers|length), ruleset_count: ($body|length), jq_matched_ids: ([$body[] | select(.name=="Protect main") | .id])}
' 2>&1 | sed -n '1,120p'Repository: Stellar-Ecosystem/lodestar
Length of output: 1627
🌐 Web query:
GitHub REST API List repository rulesets default per_page pagination
💡 Result:
When listing repository rulesets using the GitHub REST API (specifically the GET /repos/{owner}/{repo}/rulesets endpoint), the per_page parameter is used to control the number of results returned per page [1][2][3]. The default value for per_page is 30, and the maximum allowed value is 100 [1][2][3]. You can manage the pagination of results by using the per_page and page query parameters. If you need to retrieve more data, you should use the link header provided in the API response, which contains the URLs for subsequent pages of results [4].
Citations:
- 1: https://docs.github.com/en/enterprise-cloud@latest/rest/repos/rules
- 2: https://docs.github.com/en/rest/repos/rules?apiVersion=2022-11-28%5C
- 3: https://docs.github.com/en/enterprise-server@3.17/rest/repos/rules
- 4: https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api?apiVersion=2026-03-10
Paginate the ruleset lookup and stop on lookup failures.
repos/{owner}/{repo}/rulesets is paginated, so a matching ruleset beyond the first 30 results can make this script POST instead of PUT. 2>/dev/null || true also converts API failures into a create operation; let lookup failures exit instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/apply-main-branch-protection.sh` around lines 21 - 25, Update the
ruleset lookup assigned to EXISTING_ID to request all pages from the
repos/${OWNER_REPO}/rulesets API, ensuring matches beyond the first page are
found. Remove the error suppression and || true so gh API lookup failures
propagate and stop the script instead of falling through to creation.
Overview
This PR enables durable, reviewable branch protection for
mainby checking in a GitHub repository ruleset that requires all four CI jobs, one approving review, and up-to-date branches — and blocks force pushes — plus docs so contributors know the merge gate is real.Related Issue
Closes #405
Changes
🔒 Main Branch Protection
[ADD]
.github/rulesets/main-protection.jsonmain/ default branch withenforcement: active.Contract build & test,Backend test,Frontend type-check & build,Agent test.non_fast_forward) and branch deletion.[ADD]
scripts/apply-main-branch-protection.shProtect mainruleset via the GitHub API (gh).mainafter apply.[MODIFY]
CONTRIBUTING.mdVerification Results
mainrequires all CI jobs to pass before mergerequired_approving_review_count: 1);mainare blockednon_fast_forward);Maintainer action required
Because repository rulesets cannot be created without admin rights, please run once after merge (or before):
Then confirm with:
Summary by CodeRabbit
Chores
Documentation