Menu
Home Articles About Work With Me
Padlock on a chain-link fence with a blurred urban backdrop
Technology Apr 8, 2026 • 14 min read

Your GitHub Repo Is Naked. Here's How to Dress It for Production.

Branch protection, Dependabot, CI pipelines, issue templates — the complete guide to configuring a GitHub repo like a professional, even if you're the only person who will ever touch it.

Share:
Lee Foropoulos

Lee Foropoulos

14 min read

Continue where you left off?
Text size:

Contents

You wrote the code. You pushed to main. You moved on with your life. Maybe you celebrated with a coffee. Maybe you told someone about it. You felt productive.

Meanwhile, your repo is sitting on GitHub with no branch protection, no CI pipeline, no Dependabot, and no issue templates. It's the digital equivalent of a hospital gown. Technically functional. Open in the back. And everyone can see everything.

"But I'm the only one working on this repo."

Cool. You're also the only person living in your house and you still lock the door. You're the only one driving your car and you still wear a seatbelt. This isn't about other people. This is about protecting you from the most dangerous developer you'll ever work with: yourself at 2 AM.

Branch protection isn't about keeping other people out. It's about keeping you from doing something catastrophic before your morning coffee kicks in.
Open laptop showing lines of code on a dark screen in a dimly lit room
Your unprotected repo right now. Technically running. Zero guardrails. One bad force push from oblivion.

The Philosophy: Two Rulesets, One Branch

Here's the setup that makes this work. You create TWO separate rulesets for your main branch. Not one. Two. Each with a different job.

"Check Main" is the quality gate. It makes sure your code actually works before it reaches main. It requires pull requests, status checks (lint, typecheck, test, build), and code owner review. It's strict, but it has a VIP list. Your admin account and Claude AI can bypass it. Sometimes you need to get things done fast, and your AI pair programmer shouldn't have to wait for its own PR approval.

"Protect Main" is the bodyguard. It blocks force pushes and restricts branch deletion. Nobody bypasses this one. Not you. Not Claude. Not the CEO. Not God. If something tries to force push to main, the answer is always no. This is the nuclear option protection layer, and it has zero exceptions.

2
rulesets that every GitHub repo needs — one flexible, one absolute

Think of it like a nightclub. "Check Main" is the bouncer at the door who checks your ID and makes sure you're dressed appropriately, but lets the owner skip the line. "Protect Main" is the fire code. The building literally cannot hold more than the maximum capacity. The owner doesn't get to override physics.

Why Two Rulesets Instead of One?

A single ruleset forces you to choose between security and velocity. Two rulesets let you have both. The "Check" ruleset is about code quality and can be bypassed when needed. The "Protect" ruleset is about irreversible damage and can never be bypassed. Different problems, different rules.

Setting Up "Check Main" — The Quality Gate

Go to your repo on GitHub. Navigate to Settings > Rules > Rulesets and create a new branch ruleset. Name it Check Main.

Target

Set the target to Default branch (main/master). This ensures the rule applies wherever your primary branch is.

Bypass List

This is the important part. Add these bypass actors:

  • Repository Admin — that's you
  • Claude AI — if you're using Claude Code or any AI that pushes via PRs

These actors can push directly to main when the situation calls for it. Your AI assistant shouldn't need to wait for a human to approve a branch it just created. And sometimes you need to do a quick hotfix without the ceremony.

Let the admin and the AI skip the line. But make everyone else go through the door.

Branch Rules

Enable these:

1. Require a pull request before merging

This is the big one. All changes must go through a PR. Inside the additional settings:

  • Required approvals: 1 — Someone (including you) needs to approve
  • Require review from Code Owners — checked. If you have a CODEOWNERS file, the right eyes see the right files
  • Dismiss stale reviews when new commits are pushed — unchecked for solo devs (you're the only reviewer anyway)
  • Require approval of the most recent reviewable push — unchecked (same reason)
  • Require conversation resolution before merging — unchecked (useful for teams, overkill for solo)

2. Require status checks to pass

This is where your CI pipeline becomes a gate. Enable it, then configure:

  • Require branches to be up to date before merging — checked. No merging stale branches.

Add these four required status checks:

CheckWhat It Validates
lintCode style, unused imports, potential bugs
typecheckTypeScript/type errors caught before runtime
testYour test suite actually passes
buildThe project compiles and bundles correctly
4
status checks that form the minimum viable CI pipeline — lint, typecheck, test, build

Pro Tip: Name Your CI Jobs to Match

Your CI workflow job names MUST match the status check names exactly. If your workflow has a job called lint-and-format but you add a required check called lint, GitHub will never see it pass. Name your jobs lint, typecheck, test, and build. Clean. Simple. No confusion.

Allowed merge methods: Keep all three enabled (Merge, Squash, Rebase). Different situations call for different strategies. Squash for feature branches with messy commits. Merge for clean histories. Rebase when you want linear history without merge commits.

A gate in a stone wall opening to a garden path
"Check Main" is a gate, not a wall. It makes sure you're ready before you walk through. But if you have the key, you can open it.

Setting Up "Protect Main" — The Absolute Law

Create a second ruleset. Name it Protect Main.

Same target: Default branch.

Bypass list: Empty. Nobody. Zero. This is the whole point.

Branch Rules

You only need two things enabled here:

1. Restrict deletions — checked

Nobody gets to delete your main branch. Not accidentally. Not intentionally. Not "just for a second while I reorganize." The answer is no.

2. Block force pushes — checked

Force push rewrites history. On a feature branch, that's fine. On main, it's a war crime. A force push to main can erase commits that other tools, deployments, and integrations depend on. Even if you're solo, your CI, your Dependabot, and your deployment pipeline all trust that main's history is immutable.

Force pushing to main is the git equivalent of pulling a fire alarm because you're late for a meeting. Technically possible. Universally unacceptable.

Everything else in this ruleset stays unchecked:

  • Restrict creations — unchecked
  • Restrict updates — unchecked
  • Require linear history — unchecked
  • Require deployments to succeed — unchecked
  • Require signed commits — unchecked
  • Require a pull request — unchecked (that's "Check Main"'s job)
  • Require status checks — unchecked (same)
  • Require code scanning results — unchecked
  • Require code quality results — unchecked

Less is more. "Protect Main" does exactly two things, and it does them without exception.

0
bypass actors in 'Protect Main' — nobody skips the absolute rules, not even you
A heavy vault door in a bank, partially open, revealing the locking mechanism
"Protect Main" is the vault. The door is closed, the mechanism is set, and there's no override code. Not even the bank manager gets to force push.

The CI Pipeline That Makes It All Work

Those four status checks won't enforce themselves. You need a GitHub Actions workflow that runs them. Here's the standard template:

yaml
1name: CI
2on:
3  push:
4    branches: [main]
5  pull_request:
6    branches: [main]
7
8jobs:
9  lint:
10    runs-on: ubuntu-latest
11    steps:
12      - uses: actions/checkout@v4
13      - uses: pnpm/action-setup@v4
14      - uses: actions/setup-node@v4
15        with:
16          node-version: 22
17          cache: pnpm
18      - run: pnpm install --frozen-lockfile
19      - run: pnpm lint
20
21  typecheck:
22    runs-on: ubuntu-latest
23    steps:
24      - uses: actions/checkout@v4
25      - uses: pnpm/action-setup@v4
26      - uses: actions/setup-node@v4
27        with:
28          node-version: 22
29          cache: pnpm
30      - run: pnpm install --frozen-lockfile
31      - run: pnpm typecheck
32
33  test:
34    runs-on: ubuntu-latest
35    steps:
36      - uses: actions/checkout@v4
37      - uses: pnpm/action-setup@v4
38      - uses: actions/setup-node@v4
39        with:
40          node-version: 22
41          cache: pnpm
42      - run: pnpm install --frozen-lockfile
43      - run: pnpm test
44
45  build:
46    runs-on: ubuntu-latest
47    needs: [lint, typecheck, test]
48    steps:
49      - uses: actions/checkout@v4
50      - uses: pnpm/action-setup@v4
51      - uses: actions/setup-node@v4
52        with:
53          node-version: 22
54          cache: pnpm
55      - run: pnpm install --frozen-lockfile
56      - run: pnpm build

Why 4 Separate Jobs?

Running lint, typecheck, and test as separate parallel jobs means faster feedback. If your linting fails in 10 seconds, you know immediately instead of waiting for a 3-minute test suite to finish first. The build job runs last (with needs:) because there's no point building if the other checks already failed.

Notice that lint, typecheck, and test run in parallel. Build depends on all three passing. This gives you maximum speed and clear failure signals.

Adapt for your stack. Using npm? Replace pnpm with npm. Python project? Swap in pip install and pytest. The pattern is the same: separate jobs, clear names, frozen dependencies.

Dependabot: Your Automated Dependency Butler

Dependabot is GitHub's built-in tool for keeping your dependencies from rotting. Left alone, every project slowly turns into a museum of outdated packages with known vulnerabilities. Dependabot stops that.

An assembly line of precision machinery in a modern factory
Dependabot is the assembly line that keeps your dependencies fresh while you focus on actual work. It never sleeps, never forgets, and never complains about having to update lodash again.

Enabling Dependabot (Settings Page)

Go to Settings > Code security and analysis. Here's what to enable:

SettingStatusWhy
Dependency graphEnableShows you what you depend on (and what depends on you)
Dependabot alertsEnableNotifies you when a dependency has a known vulnerability
Dependabot security updatesEnableAuto-creates PRs for vulnerable dependencies
Grouped security updatesEnableBatches related security fixes into one PR instead of five
Dependabot version updatesConfigureKeeps packages current even without security issues
Dependabot malware alertsEnableCatches dependencies that have been compromised
Automatic dependency submissionDisabledOnly needed for non-standard build systems
83%
of data breaches involve a known vulnerability that had a patch available — Dependabot catches these automatically

The dependabot.yml Config

Version updates need a config file. Create .github/dependabot.yml in your repo:

yaml
1version: 2
2updates:
3  - package-ecosystem: "npm"
4    directory: "/"
5    schedule:
6      interval: "weekly"
7      day: "monday"
8      time: "09:00"
9      timezone: "America/New_York"
10    open-pull-requests-limit: 10
11    reviewers:
12      - "your-github-username"
13    labels:
14      - "dependencies"
15    groups:
16      minor-and-patch:
17        update-types:
18          - "minor"
19          - "patch"
20
21  - package-ecosystem: "github-actions"
22    directory: "/"
23    schedule:
24      interval: "weekly"
25    labels:
26      - "ci"

Grouping Is Your Best Friend

Without grouping, Dependabot creates a separate PR for every single dependency update. Monday morning becomes 15 open PRs for minor version bumps. With the groups option, minor and patch updates get batched into a single PR. You review one diff instead of fifteen. Your sanity survives.

Key settings explained:

  • schedule.interval: "weekly" — Daily is too noisy, monthly is too slow. Weekly on Monday morning gives you a clean start to the week.
  • open-pull-requests-limit: 10 — Prevents Dependabot from flooding you with PRs. If you already have 10 open, it waits.
  • groups — The grouping feature batches related updates. Group minor and patch together. Keep major version bumps separate (they're more likely to break things).
  • github-actions ecosystem — Don't forget this one. Your CI actions need updates too. actions/checkout@v3 doesn't magically become actions/checkout@v4 by itself.
Every dependency you don't update is a lottery ticket for a security breach. Dependabot just auto-plays the winning numbers for you.

Issue Templates: Make Problems Easy to Report

Even on a solo repo, issue templates save time. Six months from now, you'll open an issue that says "fix the thing" and have absolutely no idea what past-you meant. Templates force structure.

Create these files in .github/ISSUE_TEMPLATE/:

bug_report.yml:

yaml
1name: Bug Report
2description: Report a bug or unexpected behavior
3labels: ["bug"]
4body:
5  - type: textarea
6    id: description
7    attributes:
8      label: What happened?
9      description: A clear description of the bug
10    validations:
11      required: true
12  - type: textarea
13    id: expected
14    attributes:
15      label: What did you expect?
16      description: What should have happened instead
17    validations:
18      required: true
19  - type: textarea
20    id: reproduce
21    attributes:
22      label: Steps to reproduce
23      description: How can someone else trigger this bug?
24  - type: input
25    id: version
26    attributes:
27      label: Version
28      description: Which version are you running?

feature_request.yml:

yaml
1name: Feature Request
2description: Suggest a new feature or improvement
3labels: ["enhancement"]
4body:
5  - type: textarea
6    id: problem
7    attributes:
8      label: What problem does this solve?
9      description: Describe the pain point
10    validations:
11      required: true
12  - type: textarea
13    id: solution
14    attributes:
15      label: Proposed solution
16      description: How should this work?
17    validations:
18      required: true

config.yml:

yaml
1blank_issues_enabled: false
2contact_links:
3  - name: Discussions
4    url: https://github.com/your-org/your-repo/discussions
5    about: Ask questions and share ideas

Setting blank_issues_enabled: false forces everyone (including you) to use a template. No more drive-by issues that say "it's broken" with zero context.

"The best time to configure your repo was when you created it. The second best time is right now."

An architect's desk with blueprints, rulers, and precise drafting tools
Issue templates are blueprints. They don't do the work for you, but they make sure you're building on a foundation instead of guessing.

The CODEOWNERS File

If you're using the "Require review from Code Owners" setting in your "Check Main" ruleset, you need a CODEOWNERS file. For a solo dev, it's simple:

# .github/CODEOWNERS
* @your-github-username

That's it. One line. You own everything. When a PR touches any file, you're automatically requested as a reviewer. This matters because it triggers the code owner review requirement in your "Check Main" ruleset.

CODEOWNERS With AI Assistants

When Claude AI (or any AI tool) creates a PR, the CODEOWNERS file means you automatically get tagged for review. The AI can bypass the check ruleset to push directly, but the review request still fires. You still see what happened. Transparency without friction.

Putting It All Together

Here's the complete setup sequence, start to finish:

GitHub Repo Configuration Checklist 0/10
10
steps between your naked repo and a production-grade configuration. About 20 minutes of work.

The whole process takes less time than arguing about whether you actually need it. And the first time Dependabot catches a vulnerability, or your CI blocks a bad merge, or "Protect Main" stops you from accidentally force pushing at 1 AM after a long debugging session, you'll wonder why you didn't do this on day one.

A well-configured repo isn't overhead. It's a seatbelt. You don't notice it until the moment you really, really need it.

Your future self, the one who's debugging something at midnight and accidentally types git push --force origin main, will thank you. Actually, they won't even know it happened. Because "Protect Main" just quietly said no, and they moved on with their life.

That's the whole point.

How was this article?

Share

Link copied to clipboard!

You Might Also Like

Lee Foropoulos

Lee Foropoulos

Business Development Lead at Lookatmedia, fractional executive, and founder of gotHABITS.

🔔

Never Miss a Post

Get notified when new articles are published. No email required.

You will see a banner on the site when a new post is published, plus a browser notification if you allow it.

Browser notifications only. No spam, no email.