$ shellfirm

Git Branch Protection

How shellfirm applies extra protection on main, master, and production branches

Git operations on protected branches carry outsized risk. Force-pushing to main or resetting production can affect your entire team. shellfirm detects the current git branch and escalates protection when you are on a protected branch.

How it works

When shellfirm evaluates a command, it runs:

git rev-parse --abbrev-ref HEAD

This returns the current branch name. If the command is not run inside a git repository, this step is silently skipped (no error, no slowdown).

The detected branch is compared against the list of protected branch patterns. If it matches, the risk level is set to Critical.

Default protected branches

Out of the box, these branches trigger Critical risk level:

PatternMatches
mainExactly main
masterExactly master
productionExactly production
release/*Any branch starting with release/, such as release/v2.0, release/2026-Q1

Context label

When a protected branch is detected, shellfirm adds a label to the challenge banner:

Branch Context Label
branch=main

Escalation behavior

On a protected branch (Critical risk level), the challenge is escalated to at least Yes by default:

Configured challengeOn protected branch
MathYes
EnterYes
YesYes

Feature branches

Feature branches, topic branches, and any other branch not matching the protected patterns are treated as Normal risk. No escalation occurs.

Branch Risk Levels
# On feature/my-thing -- Normal risk
# On bugfix/JIRA-1234 -- Normal risk
# On main -- Critical risk
# On release/v3.0 -- Critical risk

Configuring protected branches

Edit ~/.shellfirm/settings.yaml to customize which branches are protected:

context:
  protected_branches:
    - main
    - master
    - production
    - staging
    - "release/*"
    - "hotfix/*"

Wildcard patterns

Patterns ending with /* match any branch starting with the prefix. For example, release/* matches release/v1.0, release/v2.0-rc1, and release/anything.

Only suffix wildcards are supported. Patterns like *-production or feature/*/main are not supported -- use exact names for those.

Practical examples

Force push on main

Force Push on Protected Branch (main)
# On branch: main
$ git push --force origin main
============ RISKY COMMAND DETECTED ============
Severity: HIGH
Context: branch=main
Description: Force push can overwrite remote history.
Alternative: git push --force-with-lease
(Pushes only if the remote ref matches your local tracking ref, preventing accidental overwrites.)
Challenge ESCALATED: Math -> Yes
? Type yes to continue Esc to cancel ›

Same command on a feature branch

Force Push on Feature Branch
# On branch: feature/experiment
$ git push --force origin feature/experiment
============ RISKY COMMAND DETECTED ============
Severity: HIGH
Description: Force push can overwrite remote history.
Alternative: git push --force-with-lease
(Pushes only if the remote ref matches your local tracking ref, preventing accidental overwrites.)
? Solve the challenge:: 7 + 3 = ? Esc to cancel ›

Notice the difference: on main the challenge is Yes (you must type "yes"), while on a feature branch it uses the default Math challenge.

Git reset on production

Git Reset on Production Branch
# On branch: production
$ git reset --hard HEAD~5
============ RISKY COMMAND DETECTED ============
Severity: HIGH
Context: branch=production
Description: This command going to reset all your local changes.
Alternative: git stash
(Saves your changes to the stash so you can recover them later with 'git stash pop'.)
Challenge ESCALATED: Math -> Yes
? Type yes to continue Esc to cancel ›

Team policy integration

Protected branches can also be configured in .shellfirm.yaml project policies. Team-level branch protections are merged with your global settings (union of both lists):

# .shellfirm.yaml in your repo
version: 1
context:
  protected_branches:
    - staging
    - "deploy/*"

This adds staging and deploy/* to the existing global list without removing main, master, or production.