Docs as Code on GitHub: A Workflow That Keeps Docs in the Pull Request
Docs as code means writing documentation with the same tools and review process as code. A practical GitHub workflow: repo layout, the CI checks worth running, a docs-required gate, and the gap none of them close — docs that are reviewed but no longer true.
- documentation
- developer-tools
- developer-experience
- engineering-management
Every engineering team has two kinds of documentation. There's the kind that lives in the repository, gets changed in the same pull request as the code, and is mostly right. And there's the kind that lives in a wiki, gets written in a burst of good intentions after a launch, and has been quietly wrong since roughly the second sprint after that. Nobody chose the second kind on purpose. It's just what happens when docs live somewhere the code review doesn't reach.
Docs as code is the name for choosing the first kind deliberately. Write the Docs defines it as a philosophy that you should be writing documentation with the same tools as code — issue trackers, version control, plain-text markup, code review, automated tests. The same guide names the payoff most teams actually want: you can block merging a feature that doesn't include its documentation, which gets developers writing about a change while it's still fresh instead of reconstructing it from memory three months later.
The idea is simple. The workflow is where teams stumble. This is a practical guide to running docs as code on GitHub: how to lay out the repo, which CI checks earn their place, how to enforce docs in pull requests without annoying everyone, and the one failure mode that every one of those checks misses.
What docs as code actually changes#
Moving Markdown files into a docs/ folder is not the change. The change is that documentation stops being a separate activity with a separate tool, owner, and review process, and becomes part of the unit of work that already has all three: the pull request.
| Wiki-style docs | Docs as code | |
|---|---|---|
| Where it lives | A separate tool with its own permissions | The repository, next to the code it describes |
| When it changes | Whenever someone remembers | In the same pull request as the behavior change |
| Who reviews it | Usually nobody | The same reviewers as the code, plus owners you choose |
| History | Page revisions with no link to the code | git log and git blame, tied to the commit that changed behavior |
| Quality checks | None | Linting, link checks, and style rules in CI |
| Versioning | One version: "current," whatever that means | Branches and tags; docs for v2 live with v2 |
| What agents see | Often nothing — wikis sit outside the repo | Everything; coding agents read the repo directly |
That last row is newer than the rest, and it's quietly become one of the strongest arguments for the whole approach. A coding agent working in your repository reads README.md, AGENTS.md, and whatever is in docs/. It doesn't read your wiki. If the explanation of how your auth flow works lives in Confluence, then as far as your agents are concerned it doesn't exist — and they'll infer the design from the code, including the parts you're halfway through deprecating.
The workflow, end to end#
Here's the loop a working docs-as-code setup runs on every pull request:
flowchart TD
A["Behavior change on a branch"] --> B["Docs updated in the same branch"]
B --> C["Pull request opened"]
C --> D{"Docs required for this change?"}
D -->|Yes, but no docs changed| X["Check fails: add docs or label no-docs-needed"]
X --> B
D -->|Docs present or not needed| E["Docs CI: markdownlint, Vale, link check"]
E -->|Fails| B
E --> F["Review: code owners plus docs owners"]
F --> G["Merge"]
G --> H["Docs site and README rebuild from main"]Nothing here is exotic. Every box is an off-the-shelf GitHub feature or a well-maintained open-source action. The work is in deciding what goes in each box, so let's take them in order.
Setting it up on GitHub#
1. Put docs where the code is. A layout that scales from a single service to a modest monorepo:
repo/
├── README.md # what it is, how to run it, where to look next
├── AGENTS.md # build/test commands and boundaries for coding agents
├── docs/
│ ├── architecture/ # how the system fits together, with diagrams
│ ├── decisions/ # architecture decision records, one per decision
│ ├── guides/ # task-oriented how-tos
│ └── runbooks/ # operational procedures
├── .github/
│ ├── CODEOWNERS
│ └── workflows/docs.yml
├── .vale.ini
└── src/
Two rules keep this from sprawling. First, docs sit as close to the code they describe as the tooling allows — a package-specific guide belongs in that package's directory, not in a global docs/ folder three levels away. Second, keep each file small and single-purpose; a minimal documentation template survives change far better than a 4,000-word overview. If you're writing an AGENTS.md for the first time, our AGENTS.md guide covers what belongs in it.
2. Write in plain text with diagrams as code. Markdown for prose. Mermaid for architecture and sequence diagrams, so a diagram change shows up as a readable diff instead of a binary PNG nobody can review. Decisions go in short ADRs — our free ADR generator gives you a clean template if you don't have one yet. The rule of thumb: if a reviewer can't see what changed in the diff, it isn't really docs as code.
3. Give docs owners with CODEOWNERS. A couple of lines route every docs change to the people who care about it:
# .github/CODEOWNERS
/docs/ @your-org/docs-reviewers
/README.md @your-org/docs-reviewers
Turn on Require review from Code Owners in your branch protection rules and those reviewers become a real gate, not a suggestion. Keep the team small. A docs-owner group of thirty people is a group of nobody.
4. Run the cheap mechanical checks in CI. Three tools cover almost everything a machine can check about documentation:
# .github/workflows/docs.yml
name: docs
on:
pull_request:
paths: ["docs/**", "**/*.md"]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v24
with:
globs: "**/*.md"
- uses: vale-cli/vale-action@v3
with:
files: docs
- uses: lycheeverse/lychee-action@v2
with:
args: --no-progress './**/*.md'
markdownlint checks structure — heading levels, list formatting, the stuff that breaks rendering. Vale turns your style guide into lint rules (it needs a .vale.ini at the repo root pointing at your styles). lychee catches broken links, which rot faster than anything else in a docs folder. The Vale action only annotates by default (fail_on_error is off) — leave it that way at first; a style linter that fails the build on day one teaches everyone to hate the style guide.
Enforcing docs in pull requests#
The Write the Docs promise — block merging features without docs — is the part most teams never actually implement, because the naive version is miserable. "Every PR must touch a doc" produces a steady stream of one-word README edits made purely to turn the check green.
The version that works is scoped and has an escape hatch. Require docs only when a PR touches public surface — API handlers, CLI commands, config schemas, environment variables — and let a reviewer waive it with a label when the change genuinely needs no docs:
# .github/workflows/docs-required.yml
name: docs-required
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
jobs:
docs-required:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-docs-needed') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Require docs when public surface changes
run: |
changed=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD")
if echo "$changed" | grep -qE '^(src/api/|src/cli/|config/)'; then
if ! echo "$changed" | grep -qE '^(docs/|README\.md|AGENTS\.md)'; then
echo "::error::Public surface changed but no docs did. Update docs or add the no-docs-needed label."
exit 1
fi
fi
Adjust the two path patterns to your repo, and that's the whole gate. The labeled/unlabeled triggers matter: without them, adding the waiver label doesn't re-run the check and the PR stays red.
Here's what the full stack of checks buys you — and, in the last column, what it doesn't:
| Check | Catches | Misses |
|---|---|---|
| markdownlint | Broken structure, inconsistent headings, bad lists | Anything about meaning |
| Vale | Style-guide violations, banned terms, passive voice | Whether a sentence is true |
| lychee | Dead URLs and broken relative links | Links that resolve to the wrong thing |
| Docs-required gate | Public-surface PRs that ship with zero doc changes | A doc change that's in the wrong file, or incomplete |
| CODEOWNERS review | Unclear writing, missing context, wrong audience | Docs elsewhere in the repo that the change made false |
Read that last column top to bottom and a pattern shows up.
Watch: one AWS team's move to docs as code#
If you're making the case internally, it helps to hear from a team that already made the move. In this Write the Docs talk, Marcia Riefer Johnston and Dave May walk through one AWS team's migration to a docs-as-code workflow:
The gap: reviewed is not the same as true#
Every check above validates the docs that changed. The failure that actually costs you is in the docs that didn't.
Say a PR renames the WEBHOOK_SECRET environment variable to WEBHOOK_SIGNING_SECRET. The author is conscientious: they update docs/guides/webhooks.md, the docs-required gate goes green, Vale is happy, the docs owner approves a clean diff. It merges. Meanwhile docs/runbooks/rotate-secrets.md, the deployment section of README.md, and the troubleshooting paragraph in AGENTS.md all still reference the old name. None of those files were in the diff, so no check looked at them and no reviewer saw them. Every one is now wrong, and every one passed review — back when it was right.
This is documentation drift, and docs as code doesn't prevent it. It moves drift from "the wiki is wrong" to "a file in the repo is wrong," which is better — at least the wrong file is versioned and greppable — but not solved. The checks gate the diff. Drift lives outside the diff.
It's getting more expensive, too. Coding agents treat the docs in your repo as ground truth. A runbook that references a renamed variable doesn't just confuse the next on-call engineer; it gets retrieved into an agent's context, where it clashes with the code and the agent has to guess which one wins. We covered that failure in detail in our context engineering field guide, and what it does to agent-authored PRs in reviewing AI-generated pull requests. A PR written in minutes can reproduce a stale pattern across a dozen files before anyone notices.
Closing that gap takes something that reads the code change and asks which documentation, anywhere in the repo, it just made false. You can't write that as a grep. It needs to know what each doc claims and what evidence in the code those claims rest on.
That's the job Moxie Docs does. It indexes your GitHub repository into living, source-cited documentation, so every doc is tied to the code it describes. When code changes, a coding agent can call moxie.review_change before committing and get back the docs that change makes false, along with convention breaches and new API surface that shipped undocumented. Then moxie.propose_doc_update returns the exact file and content to write into the same pull request. On the PR itself, an advisory Conventions check-run keeps its findings in one edit-in-place comment. When docs fall behind code anyway, Moxie opens reviewable Cleanup PRs — it proposes, your team merges. It slots into the docs-as-code workflow above rather than replacing it: your docs stay in git, go through your review, and pass your CI. They're also checked against the code on every change. You can see what the first index finds on your own repo with the free plan — one repository, no card required.
Measuring whether it's working#
You don't need a documentation platform to tell whether docs as code is paying off. A few cheap signals:
- Doc changes per behavior change. Of the last twenty merged PRs that touched public surface, how many also touched docs? Rising is good. This is the headline number.
- Waiver rate. How often is
no-docs-neededapplied? Occasional is healthy. On half of all PRs, either the path patterns are too broad or the team is routing around the gate. - Broken-link count on main. Run lychee on a weekly schedule against
main, not just on PRs. External links die without anyone changing a file. - "The docs were wrong" in incident reviews. Every time a postmortem or support thread says the runbook or README was wrong, note which file. Repeat offenders show you where drift concentrates.
- Time since last change for your most-read docs. A heavily used guide that hasn't changed in a year while its code changed weekly is almost certainly wrong somewhere.
None of these need a dashboard. A fifteen-minute look once a month will tell you most of it. If you want a fuller model, documentation debt covers how to measure and prioritize the backlog these signals reveal.
Frequently asked questions#
What is docs as code?#
Docs as code is the practice of writing and maintaining documentation with the same tools and workflow as software: plain-text files such as Markdown stored in version control, changed through pull requests, reviewed like code, and checked by automated tests in CI. The goal is that documentation changes alongside the code it describes, instead of in a separate tool that falls behind.
What tools do you need for a docs-as-code workflow?#
The minimum is Git, a code host with pull requests, and Markdown. Most teams add a Markdown linter such as markdownlint, a prose linter such as Vale for style rules, a link checker such as lychee, CODEOWNERS for docs review, and a static site generator if the docs are published outside the repository. Diagrams-as-code tools like Mermaid keep architecture diagrams reviewable in diffs.
Is docs as code better than a wiki?#
For documentation that describes code — architecture, APIs, runbooks, setup, conventions — usually yes. It lives next to the code, changes in the same pull request, gets reviewed, has full history, and is visible to coding agents that read the repository. Wikis still work well for content with no code to drift from, like team rituals, meeting notes, or planning docs.
How do you enforce documentation in pull requests on GitHub?#
Add a CI check that fails when a pull request changes public surface, such as API, CLI, or config paths, without also changing a documentation file, and let reviewers waive it with a label when no docs are needed. Pair it with CODEOWNERS entries for your docs directories and require code-owner review in branch protection. Avoid requiring a doc change on every PR; that mostly produces meaningless edits.
Does docs as code prevent documentation drift?#
It reduces drift but doesn't prevent it. Docs-as-code checks validate the documentation files a pull request changes. Drift usually happens in the files it doesn't change — a runbook or README elsewhere in the repo that the code change made inaccurate. Catching that takes tooling that maps documentation claims to the code they depend on and checks them when that code changes.
The short version#
Docs as code moves documentation into the repository and the pull request, so it gets the same versioning, review, and automated checks as the code it describes. On GitHub that's a short list of parts: docs next to code, CODEOWNERS for review, markdownlint, Vale, and lychee in CI, and a scoped docs-required gate with a waiver label. That setup reliably keeps the docs you change well written and well reviewed. What it can't do on its own is notice the docs you didn't change that are no longer true — and as coding agents treat everything in your repo as ground truth, those are the docs that cost the most. Put your docs in the pull request, then make sure the pull request knows which ones it just broke.
Republish or cite this article
You're welcome to republish this piece in full or in part. We just ask that you credit the original with a link back. See our republishing guidelines.
Attribution snippet
<p>This article was originally published on <a href="https://moxiedocs.com/blog/docs-as-code-workflow-for-github-teams">Moxie Docs</a>.</p>Cite this article
The Moxie Docs team. "Docs as Code on GitHub: A Workflow That Keeps Docs in the Pull Request." Moxie Docs, September 16, 2026, https://moxiedocs.com/blog/docs-as-code-workflow-for-github-teams.
Read next
Developer Onboarding Documentation Review: Is MoxieDocs Worth It?
Struggling with slow developer onboarding? We tested MoxieDocs and found out if it cuts ramp-up time by 30%—plus its hidden perks for GitHub maintainers.
7 AI Code Conventions That Keep Models and Teams Aligned
Discover essential AI code conventions that ensure models and development teams stay aligned, improving efficiency and consistency across AI projects. Practical strategies for engineering leaders and teams adopting AI.