Branch Protection - Forgejo Rules, Required Status Checks, and PR Workflows

Status: Active
Last Updated: 2026-08-26
Category: CI/CD - Phase 2: Workflow Governance
Prerequisites: forgejo-installation, woodpecker-first-pipeline, repository-setup
Time: 1-2 hours
Tags: forgejo, branch-protection, pull-request, status-checks, review, workflow

Summary

Make main trustworthy: configure Forgejo branch protection so protected branches only accept changes through reviewed PRs with passing Woodpecker pipelines. This article walks through the settings, the matching between CI checks and protection rules, and a PR workflow that keeps both humans and automation honest.

🎯 What You'll Learn

By the end of this article, you'll be able to:


Table of Contents

  1. Context / Why This Matters
  2. Implementation / Core Content
  3. Practical Examples
  4. Troubleshooting & Common Pitfalls
  5. Next Steps / Ops Actions
  6. Sources & Related

Context / Why This Matters

Your pipeline is only as good as what you let into it. If anyone (or any compromised account β€” see user-management and two-factor-authentication) can push straight to main, then every gate you built in security-scanning can be bypassed by simply not triggering it on the bad commit.

Branch protection closes that hole at the Forgejo layer: the rules live with the repository, apply to everyone including admins, and are enforced before code merges β€” independent of CI configuration.

Implementation / Core Content

Configuring protection in Forgejo

Repository β†’ Settings β†’ Branches β†’ Add New Rule:

Setting Recommended value Why
Branch name pattern main (or glob release/*) Protect long-lived branches; leave feature branches free
Enable Push to Whitelist enabled, list specific users or empty Empty whitelist = nobody pushes directly, all changes via PR
Enable Merge Whitelist enabled Control who may click merge
Require approvals 1 (homelab) / 2 (shared projects) Human checkpoint per change
Dismiss stale approvals enabled A new push invalidates prior reviews
Require signed commits optional Pairs with commit signing if you use it
Block merge on outdated branch enabled Forces rebase/merge of main before merging
Required status checks see below The CI gate

Required status checks: making CI mandatory

In the same rule, add required status checks matching your Woodpecker workflow names. Woodpecker reports statuses to Forgejo as <workflow-name> (from .woodpecker.yml) β€” for matrix workflows each leg appears separately (matrix-builds).

For this pipeline shape:

steps:
  test:
    image: node:22
    commands:
      - npm ci && npm test

Add test as a required check. Now "merge" stays grey until Woodpecker reports success for that exact commit SHA.

Critical detail: status checks are keyed to the commit. Force-pushing after a green run invalidates it β€” which is why Dismiss stale approvals + Block merge on outdated branch belong together with required checks.

Triggering pipelines on PRs

Ensure .woodpecker.yml runs on pull_request events, otherwise required checks stay forever "pending":

when:
  event: [push, pull_request]

steps:
  test:
    image: node:22
    commands:
      - npm ci && npm test

  secrets:
    image: zricethezav/gitleaks:v8
    commands:
      - gitleaks detect --source . --redact --verbose

Verify the Forgejo ↔ Woodpecker webhook wiring first β€” PR events travel over the same webhook as pushes configured during woodpecker-installation; broken webhooks are the top cause of never-ending pending checks (see webhooks).

Solo-maintainer mode

"Require approvals = 1" blocks a single-owner repo from merging its own PRs. Options:

Emergency bypass procedure

Even with strict protection you need a documented escape hatch for hotfixes:

  1. Open a PR against main anyway (protection applies to direct pushes, not PRs).
  2. If CI infrastructure itself is down, an admin uses merge-whitelist override.
  3. Immediately file a follow-up issue documenting why; retro-run CI once it's back.

Never disable the protection rule itself β€” forgotten re-enables are how protection silently disappears.

Practical Examples

Example: homelab service repo baseline

Protection rule for main:

Flow of a typical change:

git checkout -b fix/healthcheck-timeout
# ... edits ...
git push -u origin fix/healthcheck-timeout
# open PR in Forgejo UI

Expected sequence in the PR: three status checks appear within seconds (webhook fires), turn green after ~2 minutes, "Merge" button activates, one review approves, squash-merge. main now contains only CI-passed commits.

Verifying protection works

# should be rejected even for admins:
git checkout main && git commit --allow-empty -m "direct push test" && git push
# expect: ! [remote rejected] ... protected branch hook declined

Troubleshooting & Common Pitfalls

Problem Cause Fix
Merge button never enables, checks stuck "pending" Webhook for pull_request not reaching Woodpecker Test webhook delivery in Forgejo repo settings; re-sync per webhooks
Checks pass but merge still blocked Branch outdated vs main Update branch (merge/rebase main), wait for new pipeline run
Required check name doesn't match Step/workflow renamed in .woodpecker.yml Keep names stable; update rule when renaming deliberately
Admin accidentally locked out entirely No one in merge whitelist Add at least one admin account before clearing whitelists
Protection bypassed via force-push to feature→main fast-forward Rule pattern mismatch Confirm pattern matches exactly (main, anchored); avoid overly broad globs
Matrix legs block merge unpredictably One leg flaky, all legs required Fix or trim matrix (matrix-builds); don't drop the requirement

Next Steps / Ops Actions

Sources & Related

External references consulted:

Related knowledge-base articles:

Change Log

2026-08-26

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse