GitHub Ruleset Configuration for Portfolio App
Overview
This guide configures GitHub branch protection rulesets to enforce:
- Required CI checks (
ci / quality,ci / build) before merge - Required PR review
- Block force-push and branch deletion
- Automatic stale review dismissal
This implements the governance model described in ADR-0008: Portfolio App CI Quality Gates.
Prerequisites
- Admin access to
bryce-seefieldt/portfolio-apprepository - CI workflows (
ci.yml) already deployed and executing - Understanding of GitHub ruleset concepts (vs. legacy branch protection)
Step-by-Step Configuration
Step 1: Navigate to Rulesets
In GitHub:
- Go to
portfolio-apprepository - Navigate to Settings (top navigation)
- In left sidebar, find "Rules" → "Rulesets"
- Click "New ruleset" → "New branch ruleset"
Step 2: Name and Enable the Ruleset
| Field | Value |
|---|---|
| Ruleset name | main-protection |
| Enforcement | Active (not disabled or in "evaluate" mode) |
Step 3: Define Target Branch
Targeting rules:
- Click "Add targeting rule" (if not already present)
- Select:
- Operator:
Branch name is - Value:
main
- Operator:
- Confirm the target shows:
Branch name matches main
This ensures the ruleset applies only to the main branch. Other branches remain unrestricted for experimentation.
Step 4: Add Required Status Checks
In the ruleset editor:
- Scroll to "Require status checks to pass before merging"
- Click "Add checks"
- From the dropdown list, select:
- ✅
ci / quality - ✅
ci / build
- ✅
- Leave "Require branches to be up to date before merging" ON (recommended)
If checks don't appear in the dropdown, ensure:
- The CI workflow (
ci.yml) has been executed at least once - Check names in the workflow match exactly:
name: qualityandname: build - The latest commit on
mainhas successful check results
Outcome: Both checks must be green before merge is allowed.
Step 5: Add Require Pull Request Review
In the ruleset editor:
- Scroll to "Require pull request reviews before merging"
- Click to enable
- Configure:
| Setting | Value | Notes |
|---|---|---|
| Required reviewers | 1 | At minimum, one approval required |
| Require code owners review | (optional) | Disable for now; can add later with CODEOWNERS file |
| Dismiss stale reviews | ON | Auto-dismiss approvals when new commits are pushed |
| Require review from code owner | (optional) | Disable |
Outcome: At least one reviewer must approve before merge.
Step 6: Add Block Force-Push and Deletions (Optional but Recommended)
These prevent accidental or malicious damage:
-
Scroll to "Restrict who can push to matching branches" (optional)
- Can restrict to specific roles; for MVP, skip and rely on other protections
-
Enable "Block force pushes"
- Prevents overwriting history
-
Enable "Block deletions"
- Prevents accidental
git push --delete origin main
- Prevents accidental
Outcome: main branch is immutable except through PR merges.
Step 7: Review and Save
Before saving, verify:
- Ruleset name:
main-protection - Enforcement: Active
- Target:
mainbranch - Required checks:
ci / qualityandci / build - PR review required: 1 approval
- Stale review dismissal: ON
- Force-push blocked: ON
- Deletions blocked: ON
Click "Create" to save the ruleset.
Outcome: Ruleset is now active. All merges to main must satisfy these rules.
Validation
Test 1: Verify checks are required
- Create a test PR (any branch to
main) - Make a trivial change (e.g., update README)
- Push and open PR on GitHub
- Expected result:
- PR shows "Waiting for status checks" status check
- Merge button is disabled until checks pass
- After checks pass, merge button is enabled
Test 2: Verify review is required
- With a test PR (checks passing)
- Attempt to merge without any approvals
- Expected result:
- Merge button is disabled with message: "This branch requires at least 1 approval"
- Request review from another user or use your own account (if not author)
- Approve the PR
- Expected result:
- Merge button is now enabled
Test 3: Verify force-push is blocked
- Locally, attempt to force-push to
main:git push --force origin main - Expected result:
- Push is rejected:
remote: error: GH006: Protected branch update failed
- Push is rejected:
Test 4: Verify stale review dismissal
- With an approved PR (1 approval)
- Push another commit to the PR branch
- Expected result:
- Approval is automatically dismissed
- PR shows "Changes requested since last review" or "Review required"
- Merge button is disabled again until new approval
Troubleshooting
Merge button stays disabled even after checks pass
Possible causes:
-
Checks not actually passing:
- Click the checks tab and verify both
ci / qualityandci / buildshow green ✅
- Click the checks tab and verify both
-
Ruleset not active:
- Go to Settings → Rules → Rulesets
- Verify
main-protectionshows Enforcement: Active (not "Evaluate" or "Disabled")
-
Check names don't match:
- In ruleset, verify the imported checks match exactly:
ci / quality,ci / build - In workflow, verify job names are exactly
qualityandbuild
- In ruleset, verify the imported checks match exactly:
-
Other rulesets conflicting:
- Check if there are multiple rulesets targeting
main - Consolidate into a single ruleset or ensure they align
- Check if there are multiple rulesets targeting
Force-push still works
Cause: Ruleset is in "Evaluate" mode or is disabled.
Fix:
- Go to Settings → Rules → Rulesets
- Click
main-protection - Set Enforcement to Active
- Save
"This branch has 1 rules preventing it from being merged"
Diagnosis: One of the required rules is not satisfied.
Steps:
- Read the full error message to see which rule failed
- Common causes:
- CI checks not passing → wait for checks or fix errors
- Review not approved → request review and get approval
- Branch not up to date → click "Update branch" in PR
Related Artifacts
- ADR-0008: Portfolio App CI Quality Gates
- ADR-0007: Portfolio App Hosting on Vercel
- Vercel Setup Runbook
- Portfolio App Deployment Dossier
- GitHub Rulesets Documentation
Quick Reference
To update or delete this ruleset later:
- Go to Settings → Rules → Rulesets
- Click on
main-protection - Make changes or click "Delete ruleset"
- Save or confirm deletion
To temporarily disable (not recommended):
- Click
main-protection - Set Enforcement to Disabled or Evaluate
- Save
(Re-enable after testing or debugging.)