The Wiki Is a Liability: Documentation Belongs Next to the Code

The Wiki Is a Liability: Documentation Belongs Next to the Code
Photo by Tim Mossholder / Unsplash

Documentation describes a specific version of your code, so it must be versioned, reviewed and shipped with that code. A wiki on GitHub or in Azure DevOps is a second repository with its own history, its own permissions and no pull requests. That separation is the bug.

I recently came across Michael Heap’s post "The GitHub wiki is an anti-pattern". It resonated with me because I’ve seen the wiki in use in several projects I've worked on as Forward Deployed Engineer. Every time, this wiki becomes more of "another thing not to forget to update" rather than "the place to look for information". To me, it always feels disconnected from the rest of the project.

Michael Heap settled it for me with The GitHub wiki is an anti-pattern. His list of wiki benefits had one entry. Since then, the argument has only gotten stronger: coding agents now write a large share of our changes, and they can only keep documentation current if the documentation sits in the checkout they work on.

TL;DR

  • GitHub wikis (REPO.wiki.git) and Azure DevOps provisioned wikis (<ProjectName>.wiki) are separate Git repositories. They never branch or tag with your code.
  • Wiki edits skip pull requests, so required reviews, CODEOWNERS and status checks never run against them.
  • CI cannot lint, link-check or gate documentation it never sees.
  • git clone does not include the wiki, so IDEs, grep and coding agents do not see it either.
  • GitHub keeps most wikis out of search engines; Azure DevOps wikis rely on proprietary syntax.
  • The fix: a docs/ folder in the repo, reviewed by PR, checked in CI, and published via GitHub Pages or Azure DevOps "Publish code as wiki".

The one thing wikis do well

A wiki is one click away from anywhere in the repository. Heap could find no second benefit, and neither can I.

That convenience is real. The Wiki tab needs no setup, no static site generator and no pipeline. For a team that has never written docs, a low barrier matters.

But the properties that make a wiki frictionless are exactly the ones that make it unfit for software documentation. Edit in the browser, press save, live for everyone: that is publishing without the controls we insist on everywhere else in the delivery pipeline.

Problem 1: The wiki is a second repository on its own timeline

Both platforms back their wikis with Git, just not with your Git repository.

  • GitHub: the wiki is a separate repository at https://github.com/OWNER/REPO.wiki.git. You can create branches in it, but only changes pushed to the default branch are rendered (GitHub Docs).
  • Azure DevOps: the provisioned project wiki is a repository named <ProjectName>.wiki. It is hidden from the Repos dropdown and from Project Settings > Repositories; you reach it by building its URL by hand (Microsoft Learn). Current docs show its content on a wikiMain branch (Microsoft Learn).

The result is exactly one "current" version of the docs, with no relation to your release tags. Picture a library with v2.x in long-term support and v3.0 just shipped with a breaking configuration change. The wiki now describes v3. A v2 user follows it, gets an error, and has no v2 page to fall back on.

With docs in the repository, per-version documentation costs nothing:

# The configuration docs exactly as they shipped in v2.4.0
git show v2.4.0:docs/configuration.md

# Every documentation change between two releases
git diff v2.4.0 v3.0.0 -- docs/

Code and docs land in the same pull request, so git blame on a doc line points at the change that altered the behaviour. Release branches, hotfix branches and tags apply to the docs automatically.

Problem 2: Wiki edits bypass every review gate you configured

On GitHub, saving a wiki page is a commit straight to the wiki's default branch. There is no pull request, neither in the web UI nor from a fork.

  • Requests for wiki pull requests have sat in GitHub's community forum for years (#50163, #9473).
  • One user forked a repository to fix wiki errors, pushed a branch, and found it invisible on GitHub, with no way to open a PR (#38796).
  • The common workaround is an Action that syncs a repo folder into the wiki. That moves the source of truth back into the repo, which proves the point.

By default only users with write access can edit, but a public repository can open its wiki to everyone on GitHub.com (GitHub Docs).

Azure DevOps' provisioned wiki follows the same edit-in-place model: content updates happen inside the wiki itself. Only the published-as-code variant uses a pull request, even to revert a page (Microsoft Learn).

This is a security problem, not just a quality problem

Runbooks, onboarding guides and "set up your environment" pages contain commands that engineers paste into terminals holding privileged credentials: curl … | bash, az role assignment create, kubectl apply -f.

In docs/runbooks/, a change to one of those commands passes the same branch protection, required reviewers, CODEOWNERS and signed-commit rules as production code. In a wiki, any identity with write access, including a compromised account, can change one line and it is live instantly. None of your repository gates sit in front of that edit, because there is no pull request for them to attach to. The only trace is a wiki history page that nobody watches.

Treat any documentation someone will execute as code. It needs the same controls.

Security plans belong in the repo too

A security plan or threat model describes the same moving target as the code, so the same rule applies. Keeping it in docs/security/ adds three things a wiki cannot:

  • Threat-to-code traceability. A mitigation and the code implementing it merge in the same pull request, reviewed together.
  • A security posture per release. git show v2.4.0:docs/security/security-plan.md returns the plan as it stood when v2.4.0 shipped. PR approvals record who accepted which risk, and when. That is audit evidence a wiki's edit history does not provide.
  • Context for coding agents. Agents working in the checkout read the plan and can respect its controls. But docs an agent reads are also input it acts on, so an unreviewed change to docs/security/ could steer it. The review gate matters even more here.

Put the security reviewers on both the plan and the code paths it protects:

# .github/CODEOWNERS
/docs/security/   @contoso/security-champions
/src/auth/        @contoso/security-champions

A change to token validation now requires a security review, and the reviewer checks the plan against the diff.

It also brings obligations:

  • Exposure. In a public repository, every reader, fork and clone gets your attack surface and accepted risks. Keep open findings and unfixed vulnerabilities out; track them privately, for example in GitHub repository security advisories. Never put secrets or environment-specific details in the plan.
  • Diffable formats. Review only works if the diff is readable. Markdown and Mermaid diff cleanly; binary or tool-specific model files do not.
  • Not SECURITY.md. That file is the public vulnerability-reporting policy GitHub surfaces for the repository. The security plan is internal design documentation.

Problem 3: CI cannot test what it never sees

Write the Docs defines docs-as-code as writing documentation with the same tools as code: issue trackers, Git, plain-text markup, code review and automated tests (Write the Docs). One benefit it names is that you can block a feature from merging when its docs are missing.

A wiki gets none of this. Concretely, you lose:

  • Prose and Markdown linting on the diff (Vale, markdownlint). Heap called out Vale in CI as a key reason to prefer docs/.
  • Link checking (lychee), including links from docs into source files that break silently when code moves.
  • Tested code samples, so snippets fail the build instead of failing the reader.
  • Required status checks: "this PR changes src/api/ but not docs/api/" becomes a failing check, not a review comment.
  • Per-PR preview builds of the rendered docs.

GitHub Actions does offer a gollum event that fires when a wiki page is created or updated. That is detection after publication, not a gate before it.

Problem 4: Invisible to clones, IDEs and coding agents

git clone of the code repository does not include the wiki. Heap describes cloning the wiki separately as a hidden feature, and in practice almost nobody does it. That has direct consequences:

  • Offline and air-gapped work: the docs are not there.
  • Search: one grep -r or IDE search across code and docs is impossible.
  • Editor tooling: spell-check, Markdown preview and diagram preview only work on files in the workspace.

Coding agents make this worse

This is the argument that did not exist in 2022. Coding agents such as GitHub Copilot's coding agent or Claude Code work against a checkout of the repository.

With docs in docs/, the agent reads the architecture notes and ADRs before it changes code. More importantly, it updates the affected doc in the same pull request, so the reviewer sees behaviour change and documentation change side by side.

A wiki sits outside the agent's workspace. The agent cannot read it without extra tooling, and it cannot propose a reviewed change because wikis have no review flow. Agents speed up code change, so wiki docs now rot faster than ever.

Problem 5: Platform limits and lock-in

GitHub

GitHub's own documentation lists the constraints, and twice recommends GitHub Pages instead (About wikis, Editing wiki content):

Constraint

Detail

Search engine indexing

Only wikis with 500 or more stars and public editing disabled are indexed

Size

Soft limit of 5,000 files; beyond it, some pages may be inaccessible

Private repositories

Wikis need GitHub Pro, Team or Enterprise; GitHub Free covers public repos only

Images

Inserted by URL; the docs show linking to an image stored in a repository

The image point is telling: diagrams end up in the code repo anyway, while the text describing them lives somewhere else.

Azure DevOps

The provisioned wiki encourages syntax that renders nowhere else (Microsoft Learn):

  • [[_TOC_]] and [[_TOSP_]] for page and subpage tables of contents
  • ::: query-table <query-id> ::: to embed Azure Boards query results
  • ::: video ::: blocks and @<{identity-guid}> mentions
  • ::: mermaid containers with limited Mermaid support (for example, no flowchart keyword; use graph)
  • .order files that define the navigation tree
  • Line breaks inside a paragraph need two trailing spaces, unlike most Markdown renderers

None of this renders correctly on GitHub, in MkDocs or in a VS Code preview. Leaving the platform later becomes a rewrite. If you are on Azure DevOps, at least prefer the standard fenced ```mermaid block, which the wiki also accepts.

The exception: Azure DevOps "Publish code as wiki"

In Azure DevOps the problem is the provisioned wiki, not the wiki viewer. A published-as-code wiki renders Markdown from a folder in an ordinary repository branch, and it is docs-as-code with a nice UI on top (Microsoft Learn):

Capability

Provisioned wiki

Published as code

Content location

Hidden <ProjectName>.wiki repo

Any folder in your code repo

Change workflow

Edited inside the wiki

Files in Repos; revert via pull request

Multiple wikis per project

No

Yes

Versioned wikis (one per branch)

No

Yes, readers select the version

So the Azure DevOps recommendation is simple: keep docs in /docs in the code repository and publish that folder as a code wiki. You keep the viewer, Mermaid rendering and wiki search, while branch policies and PR review govern every change.

Two caveats from the same documentation:

  • Page order in a code wiki comes from .order files you maintain by hand.
  • Only the user who published the code wiki can add versions or unpublish it. Record who that is, or you inherit a bus-factor problem.

What to do instead

Put documentation in a docs/ folder on the same branch as the code, review it in the same pull request, check it in CI, and publish it from there.

1. Repository layout

repo/
├── README.md                 # what it is, quick start, link to the docs site
├── CONTRIBUTING.md
├── docs/
│   ├── index.md
│   ├── getting-started.md
│   ├── configuration.md
│   ├── runbooks/
│   │   └── rotate-signing-key.md
│   ├── adr/
│   │   └── 0007-audit-log-storage.md
│   ├── security/
│   │   ├── security-plan.md
│   │   └── security-plan-artifacts.md
│   └── images/
├── src/
└── .github/
    ├── CODEOWNERS
    ├── pull_request_template.md
    └── workflows/docs.yml

Architecture decision records (docs/adr/) benefit most: a decision and the code that implements it merge together.

2. Review ownership

# .github/CODEOWNERS
/docs/            @contoso/docs-reviewers
/docs/runbooks/   @contoso/platform-oncall

CODEOWNERS only blocks merges when branch protection or a ruleset requires code-owner review, so turn that on. Add a checklist to the PR template:

- [ ] User-facing behaviour changed → docs/ updated in this PR
- [ ] Commands in changed runbooks were executed at least once

3. Checks in CI

A minimal GitHub Actions workflow for Markdown linting and link checking:

# .github/workflows/docs.yml
name: docs
on:
  pull_request:
    paths:
      - "docs/**"
      - "**/*.md"

permissions:
  contents: read

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      # Pin third-party actions to a full commit SHA in production
      - uses: actions/checkout@v4

      - name: Markdown lint
        run: npx --yes markdownlint-cli2 "docs/**/*.md" "README.md"

      - name: Link check
        uses: lycheeverse/lychee-action@v2
        with:
          args: --no-progress "docs/**/*.md" README.md
          fail: true

Add Vale for style rules once you have a style guide (errata-ai/vale-action plus a .vale.ini). In Azure DevOps with Azure Repos, run the same two commands in a pipeline and attach it to main as a build validation branch policy with a path filter on /docs/*.

4. Publishing

  • GitHub: build docs/ with GitHub Pages, either directly or through an Actions workflow using MkDocs, Hugo or Docusaurus. Heap's warning still applies: never author in a gh-pages branch, because that detaches the docs from the code again. A build output branch as a deploy target is fine.
  • Versioned sites: generators with version support (for MkDocs, the mike plugin) build one site per release tag, so v2 users read v2 docs.
  • Azure DevOps: publish docs/ as a code wiki and add a version per release branch.
  • Close the old door: replace the wiki with a single page linking to the new docs, or disable the wiki in the repository settings.

Migrating an existing wiki without losing history

Both wiki types are Git repositories, so you can merge their full history into the code repository instead of copy-pasting pages. git subtree add imports another repository's history under a prefix.

GitHub:

git switch -c docs/import-wiki
git remote add wiki https://github.com/OWNER/REPO.wiki.git
git ls-remote --symref wiki HEAD      # shows the wiki's default branch
git subtree add --prefix=docs wiki <default-branch>
git remote remove wiki

Azure DevOps (provisioned wiki):

git switch -c docs/import-wiki
git remote add wiki https://dev.azure.com/<Organization>/<ProjectName>/_git/<ProjectName>.wiki
git ls-remote --heads wiki            # current docs show wikiMain; verify yours
git subtree add --prefix=docs wiki wikiMain
git remote remove wiki

Then clean up in the same pull request:

  1. Links: convert wiki-style page links ([[Page Name]], or links without a .md extension) into relative .md links, and let the link checker from the CI step find the rest.
  2. Navigation: GitHub's _Sidebar.md and _Footer.md, or Azure DevOps .order files, become your site generator's nav config. Keep .order if you republish the folder as an Azure DevOps code wiki.
  3. Attachments: Azure DevOps stores pasted files in a .attachments folder; move them to docs/images/ and update the paths.
  4. Proprietary syntax: replace [[_TOC_]], [[_TOSP_]], ::: query-table and ::: video blocks with standard Markdown or your generator's equivalents.
  5. Cut over: merge, publish, then replace the wiki with a single pointer page or disable it, so nobody keeps editing the old copy.

When a wiki is still acceptable

Some content is not tied to a code version and is never executed: team rituals, meeting notes, on-call rotations, a glossary spanning many repositories. A wiki is tolerable there, although a dedicated knowledge base usually serves it better than a repo-scoped wiki.

The test takes two questions:

  1. Would this page be wrong for some past release of the code?
  2. Will someone paste anything from it into a shell?

If either answer is yes, the page belongs in the repository.

Conclusion

Heap made the case against the GitHub wiki in 2022, and the case has grown since. Documentation that lives outside the repository cannot be versioned with releases, reviewed in pull requests, tested in CI or updated by the agents that now change our code. On GitHub, use docs/ plus Pages. On Azure DevOps, use docs/ plus "Publish code as wiki". The wiki tab can stay, pointing to where the real docs live.

Sources

→ Disclaimer