Build the documentation site on every branch, but publish only from main¶
- Status: accepted
- Date: 2026-10-04
Technical Story: OMG-Software/Merovingian#500
Context and Problem Statement¶
The documents under docs/ are published as a GitHub Pages site built with
MkDocs Material from mkdocs.yml. Every branch already has to update the
relevant documents, so every branch can also break the site: a renamed
document, a link to a file that moved, or a new page nobody added to the
navigation. How should branches interact with the published site?
The documents are written to be read in the repository, so they link to source
files, workflows, module AGENTS.md files and the vendored Matrix spec mirror,
none of which the site publishes. Under mkdocs build --strict each of those
links is a broken-link warning.
Decision Drivers¶
- A branch that breaks the site must fail CI before it merges, not after.
- The public site must describe what is on
main, never unmerged work. - Reviewers should be able to see the rendered site for a branch.
- The site is a security-first project's public face: the build must not run unpinned or unverified third-party code, and the deploy credentials must be as narrow as possible.
Considered Options¶
- Build strictly on every branch and pull request, keep the rendered site as a
workflow artifact, and deploy to Pages only from
main. - Publish a live preview per branch under
/preview/<branch>/on agh-pagesbranch, alongsidemainat the site root. - Deploy the live site from whichever branch was pushed last.
- Build and deploy only from
main, as the first version of the workflow did.
Decision Outcome¶
Chosen option: build strictly on every branch and pull request, deploy only
from main, because it is the only option that both catches a broken site
before merge and keeps the public site equal to main.
.github/workflows/pages.ymlruns on every push and pull request with no path filter. It runstests/tooling/test_docs_site.pyand thenmkdocs build --strict, and uploadssite/as thedocs-siteartifact.- Only the
deployjob, gated onrefs/heads/mainand notpull_request, holdspages: writeandid-token: write. Thebuildjob hascontents: readonly. requirements-docs.txtis a hash-locked resolution ofrequirements-docs.incovering every transitive package, and CI installs it withpip --require-hashes.scripts/mkdocs_site_hooks.pyrewrites links to files the site does not publish so they open on GitHub at the commit the site was built from, and links intodocs/matrix-v1.19-spec/so they open the same section ofhttps://spec.matrix.org/v1.19/. A link whose target exists nowhere in the repository is left alone, so the strict build still rejects it.tests/tooling/test_docs_site.pyfails when a published document has no navigation entry inmkdocs.yml. ADR records are exempt; the ADR index links to them.
Positive Consequences¶
- Broken links, missing navigation entries and theme warnings fail the branch that introduced them.
- Documents keep their repository-relative links. Nobody has to write a different link for the site than for GitHub.
- A reviewer can download the rendered site for any branch.
Negative Consequences¶
- There is no browsable live preview of a branch; the artifact has to be downloaded and opened locally.
- Branches pushed to this repository build the site twice when a pull request is open: once for the push and once for the pull request merge ref.
- Adding a document under
docs/now also means adding it to themkdocs.ymlnavigation or toexclude_docs.
Pros and Cons of the Options¶
Live preview per branch¶
- Good, because reviewers get a browsable URL.
- Bad, because unmerged work is published on the project's public domain, where search engines index it and readers cannot tell it from the real site.
- Bad, because Pages must deploy from a
gh-pagesbranch that every branch run can write to, so every branch build needscontents: write. - Bad, because previews of deleted branches need a separate pruning job.
Deploy from whichever branch was pushed last¶
- Good, because it is the simplest workflow.
- Bad, because the public site shows unmerged work and changes with every push.
Build and deploy only from main¶
- Good, because no extra CI runs.
- Bad, because a branch that breaks the strict build merges cleanly and only
fails the deploy on
main, after the fact.