Most docs PRs are low-risk: typo fixes, broken-link updates, minor additions. Requiring human review on every one creates friction — a writer opens a PR at 4pm, the reviewer isn't online until the next morning, the typo stays live for 18 hours. GitHub's auto-merge feature lets you flip this: the PR merges automatically once required CI checks pass, without waiting for an approving human.
This guide covers the safe defaults for docs-repo auto-merge.
When auto-merge makes sense
Typo + broken-link fixes
Low-risk by definition. CI catches MDX parse errors. Reviewer adds no value vs 18h delay.
Generated content updates
Auto-generated files (schema-reference, integration pages, llms.txt). CI catches schema drift. Human review is rubber-stamping.
Dependabot bumps
Small dependency updates. CI catches regressions. Patch + minor version bumps auto-merge; major bumps go through review.
Low-churn reference docs
A contributor fixes a value in a table. The table was auto-gen'd from code. No structural change. Safe auto-merge candidate.
When NOT to auto-merge
New pages
Structural change. Every new page should get a human look — voice, IA placement, cross-links all need judgement.
Rewrites of existing pages
Major revisions change the meaning. Need a second pair of eyes to verify accuracy.
Legal / security / billing content
Wrong wording here is expensive. Always human review.
Config schema changes
public/schema.json changes affect every tenant. Review carefully.
The safe baseline setup
Three layers:
GitHub's native auto-merge — opt-in per PR. Requires all checks + reviews (if configured).
Required CI checks — catch mechanical failures before merge.
CODEOWNERS — high-sensitivity paths still require human review even with auto-merge.
Step 1: enable auto-merge on the repo
GitHub repo settings → General → scroll to "Pull Requests" → check "Allow auto-merge".
Now PRs can opt in individually by clicking "Enable auto-merge" on the PR page.
Step 2: required CI checks
In .github/workflows/docs-ci.yml (or equivalent), run checks that catch the classes of errors that shouldn't reach main:
name: Docs CI
on:
pull_request:
paths:
- "docs/**"
- "nookdocs.config.json"
- "public/schema.json"
- "src/components/docs/**"
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
# Parse every MDX file — catches syntax errors before deploy
- name: MDX parse check
run: |
node --input-type=module -e "
import { mdxParse } from 'safe-mdx/parse';
import { readFileSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
function walk(dir) {
const out = [];
for (const f of readdirSync(dir)) {
const p = join(dir, f);
if (statSync(p).isDirectory()) out.push(...walk(p));
else if (p.endsWith('.mdx')) out.push(p);
}
return out;
}
let errors = 0;
for (const f of walk('docs')) {
try { mdxParse(readFileSync(f, 'utf-8')); }
catch(e) { errors++; console.log(\`❌ \${f}:\${e.line}: \${e.message}\`); }
}
if (errors) process.exit(1);
console.log('All MDX files parsed cleanly.');
"
# Cross-layer drift (types + validator + renderer + docs + UI)
- name: Sync check
run: npm run check:sync
# TypeScript
- name: Type check
run: npx tsc --noEmit
# JSON validity
- name: Config JSON valid
run: node -e "JSON.parse(require('fs').readFileSync('nookdocs.config.json','utf-8'))"
# Broken links (internal + external)
- name: Link check
run: npx lychee --include-file-extensions=mdx docs/ || true
# `|| true` keeps link-rot from blocking merges; flag it in the PR comment insteadThen in repo settings → Branches → main → Require status checks to pass, check "MDX parse check" + "Sync check" + "Type check" + "Config JSON valid".
Step 3: CODEOWNERS for sensitive paths
.github/CODEOWNERS forces explicit review for paths you care about:
# Default: any docs change pings the docs team (shows in PR sidebar)
/docs/ @yourorg/docs-team
# Legal / compliance — always require legal review
/docs/legal/ @yourorg/legal-team
/docs/privacy/ @yourorg/legal-team
/docs/terms/ @yourorg/legal-team
# Security — security team review
/docs/security/ @yourorg/security-team
# Schema changes — engineering review
/public/schema.json @yourorg/engineering-team
/src/types/ @yourorg/engineering-team
# Billing / pricing — product + finance review
/docs/pricing* @yourorg/finance-team
/docs/billing/ @yourorg/finance-teamPair with branch protection's "Require review from Code Owners" — now legal-team must review any /docs/legal/ change, even if auto-merge is enabled.
Step 4: enable auto-merge per PR
On each PR you'd like to auto-merge:
Open the PR.
Scroll to the merge panel at the bottom.
Click Enable auto-merge.
Pick merge strategy (squash-and-merge is the usual choice for docs).
Once required checks pass AND required reviews are satisfied, the PR merges automatically.
Auto-merge patterns
Pattern 1: auto-merge on approval
Writer opens PR. Reviewer approves. Auto-merge fires when CI completes. No "hit the button" friction.
Workflow:
Writer opens PR.
Writer clicks "Enable auto-merge" (or uses a PR template checkbox).
Reviewer approves.
CI completes.
PR merges automatically.
Saves the 1-3 minute "pinging reviewer to click merge" step.
Pattern 2: auto-merge Dependabot PRs
Most Dependabot PRs are trivial (patch + minor bumps). Auto-merge these after CI passes:
name: Dependabot auto-merge
on:
pull_request_target:
types: [opened, synchronize]
jobs:
automerge:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2
# Auto-merge only patch + minor version bumps
- name: Auto-merge
if: |
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
steps.metadata.outputs.update-type == 'version-update:semver-minor'
run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}Major version bumps still require human review.
Pattern 3: auto-generated content updates
Daily job regenerates integration pages / schema-reference / etc. Opens PR. Auto-merges if CI passes.
name: Regenerate generated content
on:
schedule:
- cron: "0 5 * * *" # daily 5am UTC
workflow_dispatch:
jobs:
regen:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
- name: Regenerate
run: |
npm run build:schema-reference
npm run build:integration-pages
- name: Open PR (auto-merge enabled)
uses: peter-evans/create-pull-request@v6
with:
commit-message: "chore(generated): refresh auto-generated docs"
branch: auto/regen-generated-pages
title: "chore(generated): daily refresh"
body: |
Automated refresh of schema-reference + integration pages.
Auto-merges on CI pass.
labels: auto-mergePairs with a workflow that enables auto-merge on PRs with the auto-merge label.
Safety net — what to do when automerge goes wrong
Inevitably, some bad content ships because auto-merge merged a PR that should have been flagged. Recovery playbook:
Identify the bad commit
git log --oneline main | head -20 on the failing branch.
Revert
git revert <commit-sha> creates a new commit that undoes the bad one. Push to main — auto-deploys within 30 seconds.
Diagnose the gap
What check should have caught this? If parse check missed it, the check is insufficient. If the reviewer wasn't required, CODEOWNERS needs expanding.
Tighten the gate
Update CI / CODEOWNERS to catch this class of error next time. Commit the change.
Reset trust
If automerge caused real damage (legal / security / billing exposure), pause auto-merge for 1-2 weeks. Force human review on everything while the gap gets fixed.
Common automerge mistakes
Related
Git concepts — foundations
Branches — when to branch vs direct-to-main
Maintenance — CI + auto-merge feed the freshness audit loop
Improving docs — reduce review cycles without reducing quality