GitHub Collaboration Best Practices
A team's pull-request flow is either a fast feedback loop or a queue where work goes to wait. The difference is rarely talent — it is whether the team enforces a few rules that keep changes small, reviews real, and the trunk protected. Skip them and review degrades into theater: rubber-stamped approvals, advisory CI, and "who broke main?" on Monday.
This page consolidates the GitHub collaboration mechanics covered earlier into the policy a team actually configures on a repository. The aim is to make the safe path also the fast path, so doing the right thing is the path of least resistance.
PR Size and Scope
Target pull requests under about four hundred changed lines. Small PRs get a careful read in hours; a two-thousand-line PR gets either ignored for days or skimmed and approved unread, which is worse than no review at all because it launders bad code through a process that looks rigorous.
When a change is genuinely large, split it into a stacked series of small PRs that each land independently, rather than one monolith that no reviewer can hold in their head.
Review Quality
Review for correctness and design — the logic, the edge cases, the API shape — and let a formatter own style so humans never argue about whitespace. Require at least one approval, use "request changes" explicitly when something must change rather than leaving a vague comment, and never approve a PR you did not actually read.
When style nits dominate a review, real bugs slip past underneath them; reviewer attention is finite, and every comment spent on a comma is a comment not spent on the off-by-one.
Branch Protection Rules
Protect main so the policy is enforced by the platform, not by goodwill. Require status checks to pass, require reviewers, dismiss stale approvals when new commits are pushed, and block force-push and deletion. Without these, CI is merely advisory and a single --force can erase history.
CODEOWNERS
Map paths to the teams that own them with a CODEOWNERS file so the right reviewers are auto-requested, and make review required on critical paths — auth, billing, infrastructure. Without it, a change to authentication code can merge without the security team ever seeing it, which is exactly the change you least want unreviewed.
PR Templates and Linked Issues
A PR template gives every change a testing checklist and a place for context, and writing "Closes #N" links the PR to its issue so the issue auto-closes on merge and the changelog inherits the reasoning. The small upfront structure pays back as searchable history when someone asks why a change was made.
Docs That Reduce Load
A CONTRIBUTING.md, a README that gets a fresh clone running, and architecture decision records for the choices people keep relitigating all do the same job: they answer a question once so it stops being asked in every PR and every onboarding. Documentation here is a multiplier, not bureaucracy.
Squash merge collapses a PR into one commit on main, giving a clean one-line-per-PR history at the cost of losing the individual development commits. Merge commit preserves the full branch history and records an explicit merge point, keeping every commit but producing a non-linear log.
Rebase merge replays the PR's commits onto main with no merge commit, yielding a linear history that keeps individual commits. Pick one strategy repo-wide and enforce it; mixing them makes the log impossible to reason about.
- Opening 1500-line PRs that sit for a week and get approved unread — review becomes theater and bugs ship with a green checkmark.
- Leaving
mainunprotected — anyone can force-push or merge without checks, so CI is advisory and history is one mistake from being rewritten. - Turning off dismiss-stale-approvals — a PR approved clean gets new commits pushed after approval and merges with the new code unreviewed.
- Shipping sensitive paths without CODEOWNERS — auth or billing code merges without the owning team ever being requested as a reviewer.
- Letting style nits dominate reviews while a formatter sits unused — reviewer attention burns on whitespace and the logic bug slips through.
- Keep PRs under about four hundred changed lines; split larger work into a stacked series.
- Enable branch protection: required checks, required reviews, dismiss-stale-approvals, and no force-push to
main. - Add a CODEOWNERS file with required review on auth, billing, and infrastructure paths.
- Automate formatting and linting in CI so human review focuses on correctness and design.
- Use a PR template with a testing checklist and "Closes #N" to link issues.
- Maintain
CONTRIBUTING.md, a runnable README, and ADRs so recurring questions are answered once.
Knowledge Check
Why do small PRs improve review quality?
- A reviewer can actually read a few hundred lines carefully, while a 2000-line PR gets skimmed or approved unread
- GitHub automatically assigns several additional reviewers to a pull request as soon as it is small enough to qualify
- A small pull request disables the merge button entirely until every required CI check has passed
- A large pull request can no longer be squash-merged at all once it crosses the team's line cap
What does dismiss-stale-approvals prevent?
- New commits pushed after an approval merging unreviewed, by invalidating the approval when the diff changes
- Reviewers from being able to approve any one of the pull requests that they themselves had originally authored
- Stale merged feature branches from being automatically deleted once the PR closes
- Required CI checks from running more than a single time over the lifetime of one PR
What role does CODEOWNERS play on sensitive paths?
- It auto-requests and can require the owning team's review, so auth or billing changes can't merge without them
- It hides the sensitive files from everyone other than their explicitly listed owners inside the pull request diff view
- It transparently encrypts at rest every file that happens to live under any of the listed paths
- It blocks all commits made to those paths whenever they land outside of normal business hours
When a linter owns style, what should human review focus on?
- Correctness and design — logic, edge cases, and API shape, not whitespace and formatting
- Catching the remaining whitespace and indentation style nits that the linter happened to miss on this run
- Verifying only that the commit message subject and body follow the required project format
- Confirming that the branch name correctly matches the team's documented naming convention
What is the tradeoff between squash merge and a merge commit?
- Squash gives one clean commit per PR but loses individual commits; a merge commit preserves full branch history at the cost of a non-linear log
- A squash merge is faster to run but can never be reverted, whereas a merge commit can be rolled back cleanly, so the choice comes down to whether you might need to undo the change after it lands on the main branch
- A squash merge requires branch protection to be enabled; a merge commit does not, so teams that skip protection rules are limited to merge commits and lose the option to collapse a PR
- There is no real difference between them once the pull request has merged, since both end up producing the same commits and the same shape of history on the target branch either way
You got correct