Skip to main content Branch Protection & PR Workflow: Quality Gates for Humans and Agents | works-on-my.cloud
Series: aws-for-startups • Part 7 of 8

Branch Protection & PR Workflow: Quality Gates for Humans and Agents

• 10 min read
aws devops startup git
Table of Contents

Your agent generates a PR that touches three Terraform files. CI runs terraform validate and tflint. Both pass. You glance at the green checkmark, click merge, and go back to building your feature. Two days later, AWS emails you: your account hit the NAT Gateway quota for the region. The agent added a NAT Gateway per subnet instead of per AZ, and your bill jumped $200/day. CI does not check business logic. CI does not check cost.

Time: About 25 minutes.

Outcome: Branch protection rules that prevent direct pushes to main, require human review before merge, and enforce status checks. Plus CODEOWNERS for critical paths, PR labels that distinguish agent work from human work, and an expanded PR template.


Why This Matters

In Part 6, you set up git conventions: conventional commits, agent attribution, a PR template. Those are conventions. They rely on everyone following the rules. This part adds enforcement.

Without branch protection, conventions are suggestions. Any developer (or agent) can push directly to main. Any PR can be merged without review. Status checks can be ignored. One git push origin main from an agent session at 2 AM, and your production Terraform state includes an untested change nobody reviewed.

Three facts drive this entire post:

  1. CI passing does not mean safe to merge. CI checks syntax, formatting, and basic validation. It does not check whether a NAT Gateway costs $32/month or $192/month. It does not check whether an IAM policy is broader than it needs to be. It does not check whether anyone actually looked at the diff.

  2. Agent PRs need different review criteria. Humans rarely create IAM policies with Action: "*". Agents do it routinely because it is the fastest path to working code. The review checklist for agent-generated infrastructure is fundamentally different from the review checklist for human code.

  3. Protection rules apply equally. Agents do not get special treatment. They go through the same branch protection, the same review process, the same status checks. The enforcement mechanism does not care who authored the code.


What We’re Building

  • Branch protection rules on main (required reviews, status checks, no force push)
  • An expanded PR template with infrastructure-specific sections
  • CODEOWNERS for infrastructure and CI paths
  • PR labels: agent-generated, agent-assisted, human

Branch Protection Rules

GitHub branch protection is the simplest, highest-value guardrail you can add. Five minutes of configuration prevents an entire class of mistakes.

Setting Up via GitHub UI

  1. Go to your repository on GitHub
  2. Navigate to Settings > Branches
  3. Click Add branch protection rule (or Add classic branch protection rule if you see rulesets)
  4. Set Branch name pattern to main
  5. Enable these settings:
    • Require a pull request before merging (checked)
    • Require approvals: 1
    • Dismiss stale pull request approvals when new commits are pushed (checked)
    • Require status checks to pass before merging (checked)
    • Require branches to be up to date before merging (checked)
    • Do not allow bypassing the above settings (checked)
  6. Under “Restrict who can push to matching branches,” leave it unrestricted for now (you are a small team)
  7. Click Create (or Save changes)

Why each rule matters:

RuleWhat It Prevents
Require PRDirect pushes to main (from humans or agents)
1 approvalMerging without anyone reviewing the diff
Dismiss stale approvalsApproving a PR, then pushing new commits that skip review
Require status checksMerging when CI is failing
Up-to-date branchesMerging a PR that conflicts with recent main changes
No bypassEven repo admins go through the process

The “no bypass” rule is important. Without it, the repo owner (you) can skip all protections. That feels convenient until the day you merge something at midnight without review and spend the next morning fixing it.

Setting Up via gh CLI

If you prefer reproducibility over clicking, use the GitHub CLI:

gh api repos/{owner}/{repo}/branches/main/protection \
  --method PUT \
  --field required_status_checks='{"strict":true,"contexts":["ci"]}' \
  --field enforce_admins=true \
  --field required_pull_request_reviews='{"dismiss_stale_reviews":true,"require_code_owner_reviews":true,"required_approving_review_count":1}' \
  --field restrictions=null \
  --field allow_force_pushes=false \
  --field allow_deletions=false

Replace {owner}/{repo} with your actual repository path. The contexts array under required_status_checks should list your CI job names. If your CI workflow is called ci, use ["ci"]. If you have multiple jobs, list them all.

Tip

Store this command in a scripts/setup-branch-protection.sh file in your repo. When you create new repositories (or new protected branches for staging), you run the script instead of clicking through the UI again.


Expanding the PR Template

In Part 6, you created .github/pull_request_template.md with a Generation Method section and an Agent Review Checklist. That template works for application code. Infrastructure changes need more.

Create a second template for infrastructure-heavy PRs:

## Summary

<!-- What infrastructure does this PR change? Which Terraform modules? -->

## Generation Method

- [ ] Fully human-written
- [ ] Human-written with agent suggestions
- [ ] Agent-generated with human review
- [ ] Agent-generated with minimal review

## Infrastructure Impact

### Resources Changed
<!-- List resources being created, modified, or destroyed -->

-

### Cost Impact
<!-- Estimate monthly cost change. Use `infracost` if available, otherwise estimate manually. -->

- Before: $___/month
- After: $___/month
- Delta: $___/month

### Blast Radius
<!-- What breaks if this change is wrong? -->

- [ ] Affects production traffic
- [ ] Modifies IAM permissions
- [ ] Changes networking (VPC, subnets, security groups)
- [ ] Modifies database configuration
- [ ] None of the above

## Agent Review Checklist

<!-- Complete if any agent involvement -->

- [ ] IAM permissions follow least privilege (no `Action: "*"` or `Resource: "*"`)
- [ ] No hardcoded account IDs, regions, or AMI IDs
- [ ] All resources have required tags (Environment, Project, Owner, ManagedBy)
- [ ] Security group ingress rules are scoped (no `0.0.0.0/0` except ALB 443)
- [ ] No credentials or secrets in code
- [ ] Cost implications reviewed (instance types, storage, NAT Gateways)
- [ ] Resource naming follows `{project}-{env}-{resource}` convention
- [ ] Commit messages follow conventional format with Co-Authored-By

## Terraform Plan Output

<!-- Paste the output of `terraform plan` -->

Plan: X to add, Y to change, Z to destroy.


## Testing

<!-- How was this tested? -->

-

The key difference is the Infrastructure Impact section. It forces the PR author (human or agent) to state the cost delta and blast radius before anyone reviews the code. A reviewer who sees “Modifies IAM permissions” and “Affects production traffic” reads the diff differently than one who sees “None of the above.”

Using Multiple Templates

When you have multiple PR templates in .github/PULL_REQUEST_TEMPLATE/, GitHub does not auto-populate any of them. Contributors select a template by appending ?template=infrastructure.md to the PR creation URL, or by choosing from the template picker.

Keep the default template at .github/pull_request_template.md (the one from Part 6) for general use. Use the infrastructure template when the PR touches terraform/, infra/, or .github/workflows/.

.github/ pull_request_template.md PULL_REQUEST_TEMPLATE/ infrastructure.md

Note

Coming in Part 27: Your CI pipeline will auto-detect infrastructure PRs based on changed file paths and apply the infrastructure template checklist as required status checks.


CODEOWNERS

CODEOWNERS assigns reviewers automatically based on which files a PR touches. When someone (or something) modifies terraform/, the infrastructure lead gets tagged for review without anyone remembering to do it manually.

Create a CODEOWNERS file at the root of your repository:

# Default: the repo owner reviews everything
*                       @your-github-username

# Infrastructure: requires infrastructure-aware reviewer
/terraform/             @your-github-username
/infra/                 @your-github-username
*.tf                    @your-github-username

# CI/CD: changes to workflows need review
/.github/               @your-github-username

# Secrets and credentials: always reviewed
*.pem                   @your-github-username
*.key                   @your-github-username

For a solo founder or two-person team, every path points to you. That is fine. The value is not in distributing reviews across a large team. The value is that CODEOWNERS combined with the “Require review from Code Owners” branch protection setting guarantees that infrastructure changes cannot be merged without your explicit approval.

As your team grows, CODEOWNERS scales with you:

# Application code
/src/                   @frontend-team
/api/                   @backend-team

# Infrastructure: senior engineer or founder
/terraform/             @infra-lead
/infra/                 @infra-lead

# CI/CD
/.github/               @platform-team

# Security-sensitive
*.tf                    @infra-lead @security-reviewer
🚨 Agent Trap

Agents generate CODEOWNERS files with teams that do not exist in your GitHub organization. The agent pulls team names from its training data (@devops-team, @platform-engineering, @sre-team) instead of your actual GitHub usernames or teams. GitHub silently ignores invalid CODEOWNERS entries, so you think you have review requirements, but nobody gets tagged.

What catches it: Run gh api repos/{owner}/{repo}/codeowners/errors after committing. GitHub validates CODEOWNERS and reports invalid patterns or unknown users.

Enabling CODEOWNERS in Branch Protection

CODEOWNERS only works if you enable “Require review from Code Owners” in your branch protection settings. Go back to Settings > Branches > Edit your main protection rule, and check Require review from Code Owners. Without this, CODEOWNERS is a suggestion file that GitHub ignores.


Agent PR Labels

Labels give you a way to filter, search, and eventually automate based on how code was created. Create three labels in your repository:

gh label create "agent-generated" \
  --description "PR contains fully agent-generated code" \
  --color "7057ff"

gh label create "agent-assisted" \
  --description "PR contains agent-assisted code with human edits" \
  --color "0075ca"

gh label create "human" \
  --description "PR is fully human-written" \
  --color "008672"

For now, you apply these manually when creating a PR. The label tells reviewers what lens to use:

LabelReview Approach
humanStandard code review: logic, architecture, edge cases
agent-assistedStandard review plus spot-check for agent patterns (broad permissions, hardcoded values)
agent-generatedFull agent review checklist: IAM scope, cost, naming, tags, credentials, resource sizing

The difference is time. A human PR might take 10 minutes to review. An agent-generated PR of the same size takes 20-30 minutes because you are checking for a different class of mistakes.

Note

Coming in Part 27: CI will automatically detect Co-Authored-By trailers from Part 6 and apply the correct label. No manual labeling needed.


Putting It All Together

Here is the workflow after this part:

  1. Create a branch following the naming convention from Part 6 (feature/, fix/, chore/)
  2. Make changes (you or your agent)
  3. Commit with conventional format and Co-Authored-By if agent-involved
  4. Push the branch and open a PR (select the infrastructure template if applicable)
  5. Apply the appropriate label (human, agent-assisted, agent-generated)
  6. CODEOWNERS auto-assigns the reviewer
  7. Reviewer uses the label to decide review depth
  8. Status checks must pass (CI green)
  9. Reviewer approves
  10. Merge to main

No step is optional. Branch protection enforces steps 8-10. Conventions enforce 1-5. CODEOWNERS enforces 6. The review depth in step 7 is the only part that relies on human judgment, and that is exactly where human judgment belongs.


The Fine Line

⚠️ The Fine Line
❌ UnderNo branch protection. Anyone pushes directly to main. Agent merges its own PR at 3 AM. Nobody reviews infrastructure changes until the bill arrives.
✅ Right1 required reviewer. Status checks enforced. PR template with generation method and infrastructure impact. CODEOWNERS on terraform/ and .github/. Agent PR labels for review depth.
❌ Over3 required approvers, mandatory pair programming sessions, separate approval workflows for every file path, CODEOWNERS with 12 different teams for a 3-person startup.
🤖 Agent TrapAgent creates a PR with the title “Update infrastructure” and a body that says “Made some changes to the Terraform configuration.” No cost impact, no blast radius, no context. The reviewer has to reverse-engineer intent from the diff, which means the PR either gets rubber-stamped or sits unreviewed for days.

What’s Coming

Next in Part 8: Pre-Commit Hooks & Code Quality, we add automated enforcement for everything that is still convention-only. Commitlint rejects bad commit messages before they reach your branch. Gitleaks catches secrets before they reach GitHub. Terraform validation runs before you push. The guardrails move from “please follow the rules” to “you cannot break the rules.”


Validation Checklist

Branch Protection

PR Workflow

Labels

0 of 0 completed


Key Takeaways

  1. CI passing means the code is syntactically valid, not that it is safe, cost-effective, or correctly scoped. Branch protection with required reviews ensures a human verifies what automation cannot.
  2. PR templates should force context before review starts: generation method, cost impact, blast radius. The reviewer’s job is to evaluate, not to investigate.
  3. CODEOWNERS guarantees that infrastructure changes always route to someone who understands the cost and security implications, regardless of who (or what) generated the code.

Have questions? Found an error? Open an issue or reach out on LinkedIn.

Related Posts

Series • Part 1

Your First 60 Minutes in AWS: What Everyone Skips and Regrets Later

What everyone skips and regrets later. Set up your AWS account the right way: secure enough to sleep at night, without the enterprise overhead that kills velocity.

• 12 min read • aws • devops • startup
Series • Part 2

IAM: The Key to Everything

Least privilege without losing your mind. Build a working IAM setup for your startup, not enterprise complexity, not 'just use AdministratorAccess.' The practical middle ground.

• 26 min read • aws • iam • security
Series • Part 3

Your Local AWS Setup: Stop Hardcoding Access Keys Forever

Stop hardcoding access keys forever. Configure your local development environment the right way: AWS CLI, IAM Identity Center, and credential security that scales with your team.

• 14 min read • aws • cli • security
Series • Part 4

Terraform Fundamentals: Why Clicking Is Technical Debt

Stop clicking through the AWS console. Set up your Terraform project from scratch with remote state, proper structure, and templates for everything we've built so far.

• 15 min read • aws • terraform • iac
Series • Part 5

OpenTelemetry Setup: Observability That Grows With You

Set up the OTel Collector once, choose your backend. SigNoz or Grafana stack. Your observability foundation for the entire series.

• 16 min read • aws • opentelemetry • observability
Series • Part 6

Git & GitHub: The Foundation Your Whole Team Builds On

The foundation your whole team, humans and agents, builds on. Git conventions, agent commit hygiene, and attribution that tracks who wrote what.

• 11 min read • aws • devops • startup
Series • Part 8

Pre-Commit Hooks & Code Quality: Your Agents' First Guardrail

Your agents' first guardrail, and your Scorecard's first data source. Pre-commit hooks that catch what human review misses, especially from AI-generated code.

• 20 min read • aws • devops • startup
Published: February 26, 2026