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
mainwhen the project uses versioned releases. Otherwise the latest commit onmainis 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
mainbefore opening the PR; do not mergemainback 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 #123so 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.
mainhistory reads as one commit per PR, which keepsgit loglegible 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 tomain.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: writeSet 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 }}.