Overview

GitHub is the source of truth and the deploy trigger for most projects on this site. The rules below cover branching, pull requests, commits, Actions, and the protected-branch settings that keep main green. They assume a small team or a solo author shipping to main via short-lived branches.

main is the deploy branch

main is what production runs. Treat it as immutable from local machines.

  • Never push directly to main. Land work via pull request.
  • Protect main (Settings - Branches - Branch protection): require a pull request, require status checks to pass, require branches to be up to date, and disallow force pushes.
  • Tag releases off main when the project uses versioned releases. Otherwise the latest commit on main is the release.

Use short-lived feature branches

One branch per task. Delete after merge.

  • feat/<name> for human-authored features. fix/<name> for bug fixes.
  • claude/<name> for branches that originate in a Claude Code session. The prefix makes provenance obvious during review.
  • Keep branches under a week old. Long-lived branches drift and produce painful merges. If a branch outgrows a week, split it.
  • Rebase onto main before opening the PR; do not merge main back into the feature branch.

Open PRs as drafts until they are ready

A draft PR signals “in progress.” A ready PR signals “review me.”

  • Open as draft on the first push. Promote to ready when CI is green and the description is complete.
  • Title is one line, sentence case, with the Conventional Commits scope: feat(tooling): add Quartz CNAME plugin.
  • Body has two sections at minimum: a summary of what changed and a test plan. The test plan is a checklist the reviewer can run.
  • Link related issues with Closes #123 so merging the PR closes the issue.

Conventional Commits on every commit

Commit messages follow Conventional Commits.

feat(scope): short imperative summary
fix(scope): short imperative summary
chore(scope): short imperative summary
docs(scope): short imperative summary

Common types: feat, fix, chore, docs, refactor, test, ci. Scope is the area of the codebase: tooling, frontend, ops, or a package name. Keep the summary under 72 characters; put detail in the body.

Squash merge by default

Choose one merge strategy and stick with it.

  • Default to squash merge. main history reads as one commit per PR, which keeps git log legible and makes reverts trivial.
  • Use merge commits only when the feature branch’s history is meaningful (a long-running effort with intentional commits worth preserving).
  • Disable rebase merge to avoid two merge styles in one repo.

Set the default in Settings - General - Pull Requests, and disable the other strategies.

One workflow per concern

Actions workflows are easier to maintain when each file owns one job.

  • deploy.yml: build and deploy on push to main.
  • ci.yml: lint and test on every pull request.
  • release.yml: cut a release on tag push.

Keep workflow YAML under 100 lines. If a job grows past that, extract a composite action or a reusable workflow.

Lock down GITHUB_TOKEN permissions

The default GITHUB_TOKEN has broader permissions than most jobs need. Scope down at the workflow level.

permissions:
  contents: read
  pages: write
  id-token: write

Set the minimum scope per workflow. For deploy workflows that publish to GitHub Pages, pages: write and id-token: write are required. For lint and test workflows, contents: read is usually enough. Store secrets that exceed GITHUB_TOKEN scope in Settings - Secrets - Actions and reference them as ${{ secrets.NAME }}.