Updated · 9 min read
Best documentation automation software
Compare documentation automation software for software teams: which tools regenerate references, which sync code snippets, and which update guides after a pull request merges.
Updated · 8 min read
Use GitHub Actions to regenerate references, remind authors, dispatch merged changes to a docs repository, draft docs pull requests, and gate docs quality.
The short answer: the core pattern for CI-triggered documentation updates has three parts. First, use paths filters so only changes to public surfaces (API specs, SDK exports, CLI commands, config schemas) start a docs job. Second, on merge, send a repository_dispatch event from the product repository to the docs repository. Third, have a workflow in the docs repository turn that event into a docs pull request, drafted by a person or by a docs agent. Add a quality gate on the docs repository so a broken docs build fails before it ships. The GitHub Actions examples below cover each step, generic first and then with Thally.
CI is good at noticing that something changed and running a job. It is not good at knowing what the change means for a reader. That splits the work into two kinds of jobs:
| Job | CI alone | Needs judgment |
|---|---|---|
| Regenerate API reference from a spec | Yes | No |
| Remind the author when docs are missing | Yes | No |
| Notify the docs repository of a merge | Yes | No |
| Decide which guides a change made wrong | No | Yes |
| Draft the updated wording | No | Yes |
The examples start with the jobs CI handles on its own, then connect CI to something that can make the judgment call.
If your docs site renders the API reference from openapi.yaml, the only thing CI has to do is rebuild the docs when the spec changes. With the spec in the docs repository, a normal deploy on push is enough. If the spec lives in the product repository, open a PR that copies it into the docs repository whenever it changes:
name: Sync OpenAPI spec to docs
on:
push:
branches: [main]
paths: ["openapi.yaml"]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Open a PR in the docs repo
env:
GH_TOKEN: ${{ secrets.DOCS_REPO_TOKEN }}
run: |
gh auth setup-git
gh repo clone OWNER/DOCS-REPO docs
cp openapi.yaml docs/openapi.yaml
cd docs
git checkout -b "spec-sync-${GITHUB_SHA::7}"
git add openapi.yaml
git -c user.name="docs-bot" -c user.email="docs-bot@users.noreply.github.com" \
commit -m "Sync OpenAPI spec from ${GITHUB_SHA::7}"
git push origin HEAD
gh pr create --fill
DOCS_REPO_TOKEN is a fine-grained token with contents and pull request write access on the docs repository only.
This job runs on pull requests that touch public-surface paths and comments when nothing under docs/ changed. Keep it a reminder rather than a required check. Plenty of code changes need no docs, and a hard block teaches people to add empty edits.
name: Docs reminder
on:
pull_request:
paths:
- "openapi.yaml"
- "src/public-api/**"
- "src/cli/**"
jobs:
remind:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Comment if docs did not change
env:
GH_TOKEN: ${{ github.token }}
BASE_REF: ${{ github.base_ref }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
if ! git diff --name-only "origin/$BASE_REF...HEAD" | grep -q '^docs/'; then
gh pr comment "$PR_NUMBER" --body "This PR changes a public surface. Does it change anything users read about? If so, link the docs update."
fi
Pass values through env instead of expanding ${{ }} inside the script. It keeps untrusted PR content out of the shell. This version comments on every push to the PR. If that gets noisy, add a label on the first run and skip when it is present.
When docs live in their own repository, the product repository needs a way to say "this merged, take a look." repository_dispatch does that without either repository needing access to the other's code.
In the product repository:
name: Notify docs of merged changes
on:
pull_request:
types: [closed]
branches: [main]
paths:
- "openapi.yaml"
- "src/public-api/**"
- "src/cli/**"
jobs:
dispatch:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Send a dispatch to the docs repo
env:
GH_TOKEN: ${{ secrets.DOCS_DISPATCH_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
gh api repos/OWNER/DOCS-REPO/dispatches \
-f event_type=product-change \
-f "client_payload[pr_url]=$PR_URL"
In the docs repository, a workflow listens for it:
name: Handle product changes
on:
repository_dispatch:
types: [product-change]
jobs:
triage:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Open a docs triage issue
env:
GH_TOKEN: ${{ github.token }}
PR_URL: ${{ github.event.client_payload.pr_url }}
run: |
gh issue create --repo "$GITHUB_REPOSITORY" \
--title "Docs check for $PR_URL" \
--body "A public-surface change merged: $PR_URL. Check which pages it affects."
This is where plain CI runs out. The issue tells someone to look. It does not say which of your pages are now wrong, and it does not write the fix.
To go from "someone should look" to "here is the proposed update," replace the triage issue with a job that reads the merged change, finds affected pages, and opens a docs PR for review.
This is the job Thally Track does. It replaces steps 3 and 4 with a GitHub App, so there are no dispatch tokens or workflow files to maintain:
docs-preview label to an open pull request, and reviewers see the docs change alongside the code.Every change arrives as a pull request that a person reviews and merges.
The quickest way to see whether this beats a triage issue is to run it on changes you have already shipped. The Track demo connects to GitHub, reads your last five merged pull requests, and shows which pages each one affected. No Thally account is needed, and it only reads the repositories you grant.
The last job runs on pull requests in the docs repository itself, so automated and human edits face the same bar:
name: Docs checks
on: pull_request
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx thally check --ci --agents --min 80
thally check validates content and links and posts GitHub annotations with --ci. --agents adds the agent-readiness score, and the job fails if the score falls below --min. Outside Thally, the equivalent is a link checker, a prose linter such as Vale, and a production build.
| Trigger | Where | Job |
|---|---|---|
Spec changes on main | Product repo | PR the spec into the docs repo |
| PR touches public surfaces | Product repo | Remind the author about docs |
| PR merges | Product repo | Dispatch to the docs repo |
| Dispatch received | Docs repo | Draft a docs PR, or open a triage issue |
| Any docs PR | Docs repo | Validate content and gate on quality |
Start with steps 1 and 2. They cost nothing and catch the obvious cases. Add steps 3 and 4 when changes land faster than the team can trace them by hand. For the reasoning behind each layer, read how to keep your docs site in sync when code changes.
Workflow syntax follows the GitHub Actions documentation for repository_dispatch and path filters. Thally commands were checked against the current CLI source and CLI reference. Checked September 24, 2026.
Published under the Thally editorial policy. Technical conformance is defined in the agent-readiness methodology.
Create a managed site, or use the open-source engine to run Thally yourself.