See it as a diagram
Everything below, as a diagram you can edit. Describe yours and see it in seconds.
No account needed · Editable canvas, not a picture
Three patterns, and where each one breaks
Render and commit back. A job runs on every push, renders the diagram source to an image, and commits the result. It works for a week. Then two branches both regenerate the image and you get a binary conflict that nobody can resolve by reading the diff. Then the bot commit triggers the pipeline again and you add a skip marker to break the loop. Then someone force-pushes and the image is from a commit that no longer exists. Most of all, the source is still hand-authored, so the automation guarantees the picture matches the file, not that the file matches the system.
Fail the build on staleness. A check compares a hash of the code against a hash recorded next to the diagram and fails when they diverge. This is much better in principle and noisy in practice, because almost every change to the codebase touches the hash while almost none of them change the architecture. Engineers learn to regenerate mechanically without looking, which produces a diagram that is technically current and never reviewed.
Generate out of band. A scheduled job regenerates from the default branch, and nothing about it can block a merge. Freshness drops from instant to a day, which is well inside the tolerance of any documentation reader, and the failure mode is a stale picture rather than a red pipeline on an unrelated pull request.
# The render-and-commit pattern, with the guards it eventually needs.
name: diagram
on:
push:
branches: [main]
jobs:
render:
# without this guard the bot commit below re-triggers this workflow
if: "!contains(github.event.head_commit.message, '[skip ci]')"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./scripts/render-diagram.sh > docs/architecture.png
- run: |
git config user.name "ci"
git config user.email "[email protected]"
git add docs/architecture.png
git diff --staged --quiet || \
git commit -m "chore: regenerate diagram [skip ci]"
git pushKeep the blocking checks for facts a machine can verify
CI is good at binary questions with an unambiguous answer. Does every environment variable named in the docs still exist in the config schema. Does every directory named in ARCHITECTURE.md still exist. Does every internal link resolve. Does the OpenAPI document still match the handlers. These fail rarely, and when they fail the author knows exactly what to fix.
CI is bad at judging whether a picture communicates the system. There is no assertion for that, so teams substitute a proxy such as file age or content hash, and proxies produce false failures. A false failure on a documentation check trains people to regenerate without reading, which is worse than having no check because it manufactures the appearance of review.
A practical split: block the merge on the checkable facts, and let the picture regenerate on a schedule. See docs checks in CI for the checks that are worth blocking on.
Regenerating from the repository instead of maintaining a job
The reason the CI job exists is that the diagram is downstream of the code and nobody wants to hand-carry the update. That is exactly what repository sync does without a job in your pipeline. Connect GitHub, GitLab or Azure DevOps read only, choose a branch or a tag, and the diagram plus an architecture doc are regenerated daily.
Three behaviours matter for automation specifically. Each sync hashes what feeds the diagram, the parsed Terraform draw plan or the assembled corpus, into a structure fingerprint; when the fingerprint is unchanged the run stops before generation, so a dependency bump or a comment fix costs nothing and produces no new picture for anyone to review. A node dragged more than twelve pixels from where the previous sync left it is recorded as hand-placed and restored after the redraw, so automation does not undo layout work someone did on purpose. And because the diagram is served as a live embed, no image is committed anywhere, which removes binary conflicts and bot commits entirely.
When you do want a refresh at a specific moment, such as immediately after a release tag, trigger it from an agent rather than from a shell script. The MCP server exposes thirty-six tools to Claude Code, Cursor, ChatGPT and Claude Desktop, nine of them outcome-level; repo_status reports which repository and ref a diagram tracks, when it last synced and the commit it reflects, and repo_refresh syncs it now. A release checklist step becomes a sentence instead of a maintained YAML file.
Nothing in the pipeline to maintain
If you keep the CI job anyway
Commit the diagram source, never the rendered output, and render at read time. That alone removes binary conflicts and makes every diagram change reviewable as a text diff.
Run the job on the default branch only. Regenerating on pull request branches multiplies the failure modes and produces images for commits that will be squashed away.
Pin the renderer version. Diagram layout engines change their output between releases, and an unpinned renderer produces enormous diffs that have nothing to do with your architecture, which is how teams end up ignoring the diagram change in every review.
FAQ
Should CI regenerate the architecture diagram on every pull request?
What goes wrong when CI commits a rendered diagram back to the repository?
What documentation checks are worth failing a build on?
How can a diagram stay current without a CI job at all?
Can a diagram refresh be triggered at a specific moment, like after a release?
Why do generated diagrams produce huge diffs even when nothing changed?