Skip to content
Blume
Esc
↑↓navigate↵open⌘Jpreview
Guides

Agents

Keep documentation updated from merged code changes

A coding agent reads your merged pull requests, fixes only the docs they made wrong, shows its evidence in a pull request for review, and runs weekly in GitHub Actions.

By 13 min read

To keep docs current as code merges, give a coding agent a narrow job and review what it produces. Blume ships an agent skill for exactly this, blume-update-docs. You point the agent at a set of merged pull requests. It pulls out the user-facing changes, compares them with your docs, and edits only the pages that are now factually wrong. Then it runs blume build and blume validate and opens a pull request for a person to review. When nothing drifted, it reports what it checked and opens nothing.

In this guide you test the skill on a drift you planted, read its patch against the code change behind it, turn the patch into a pull request that shows its evidence, and confirm that a second run is a clean no-op. Only then do you schedule it in GitHub Actions. The agent and the scheduler are yours: Blume ships the skill and the checks, not a hosted runner.

The skill reads merged pull requests from the repository it runs in, so this guide assumes your code and your Blume docs share one. It keeps hand-written pages current: a reference generated from an OpenAPI spec already rebuilds on every build (see keeping API docs in sync in CI). If your team reliably updates docs in the same pull request as the code, a review checklist may be enough, and the skill is a backstop.

What the skill changes, and what it leaves alone

The skill treats this as maintenance, not authorship. Its checklist spells out when an edit is warranted:

It edits a page whenIt leaves the page alone when
A command, flag, config option, environment variable, route, prop, or default changedThe only possible change is wording, polish, or formatting
A documented workflow no longer works or misses a stepThe work is behind a feature flag that's off for your readers
A shipped capability is missing from the page that should cover itThe merged change isn't user-facing, like a refactor
A link points at moved or removed documentationThe edit would only duplicate another page or guess at future work

It keeps diffs small, copies names and values from the code instead of paraphrasing them, and never touches .blume/ or dist/. Every run ends one of two ways: a blume/* pull request, or a report with no branch, commit, or pull request.

Install the skill

From the root of the repository, install it for Claude Code:

npx skills add haydenbleasel/blume --skill blume-update-docs --agent claude-code

That puts the skill in .claude/skills/blume-update-docs/ and records it in skills-lock.json. Commit both, so every clone and the CI runner later use the same copy, and run npx skills update when you want a newer one. For another agent, pass its name instead, like --agent codex or --agent cursor. The skill also ships inside the blume package, at node_modules/blume/skills/blume-update-docs/SKILL.md, so you can point any agent at that file instead.

Plant a drift you can check

Your first run needs a known right answer. Pick a recent pull request that you know changed documented behavior, or make a small one for the test. This guide's example is the fictional Acme Messages SDK, with its code in src/ and its Blume docs in docs/. This is the page the test targets:

---
title: Configuration
description: The options you can pass to createClient.
---

Create one client and reuse it across your app:

```ts
import { createClient } from "@acme/messages";

const acme = createClient({
  apiKey: process.env.ACME_API_KEY,
  retries: 3,
});
```

| Option | Default | Description |
| --- | --- | --- |
| `apiKey` | none | Your secret API key. Required. |
| `timeout` | `10000` | Milliseconds before a request is aborted. |
| `retries` | `2` | How many times to retry a failed request. |

Three pull requests then merge without touching docs/:

  • #41 renames the retries option to maxRetries and raises the default timeout. This is the drift.
  • #42 adds messages.sendBatch() behind a flag that is off. The docs should stay silent about it.
  • #43 moves the retry backoff into its own file. Nothing a user sees changes.

This is #41, the change the docs have to follow:

@@ -3,10 +3,10 @@ import { flags } from "./flags.ts";
 export interface ClientOptions {
   /** Your secret API key. */
   apiKey: string;
-  /** Milliseconds before a request is aborted. Defaults to 10000. */
+  /** Milliseconds before a request is aborted. Defaults to 30000. */
   timeout?: number;
   /** How many times to retry a failed request. Defaults to 2. */
-  retries?: number;
+  maxRetries?: number;
 }
@@ -20,8 +20,8 @@ const delay = (attempt: number) =>
 export function createClient({
   apiKey,
-  timeout = 10_000,
-  retries = 2,
+  timeout = 30_000,
+  maxRetries = 2,
 }: ClientOptions) {

And this is the flag file from #42. The new sendBatch() throws unless batchSend is on:

export const flags = {
  /** Batch sending. Off until the batch endpoint is generally available. */
  batchSend: false,
};

The agent can't tell a released feature from a hidden one unless you say where your flags live. Put that in the file your agents already read, so every run sees it, local or scheduled. The skill reads AGENTS.md and CLAUDE.md before it does anything else:

# AGENTS.md

- Product code is in `src/`. Docs are Blume pages in `docs/`.
- Feature flags live in `src/flags.ts`. A flag set to `false` means the feature is unreleased.

If your flags live in a service the agent can't read, list the flags that are on in production in AGENTS.md, or state there that every flagged change is unreleased.

Run it on a bounded change set

Open your agent at the repository root and give it a scope you can check by hand. Name the pull requests, keep the first run a dry run, and ask for the evidence:

Use the blume-update-docs skill.

Scope: only pull requests #41, #42, and #43. Don't review any other history.
Feature flags live in src/flags.ts. A change gated by a flag that is false there is unreleased: skip it and say why.
This is a dry run: edit the docs in the working tree, but don't create a branch, commit, push, or open a pull request.

In your report, list the exact commands you used to read history, then every pull request in scope with one verdict: docs updated, already accurate, or skipped (with the reason).

Named pull requests replace the skill's default window of the last 7 days. The dry-run line stops the skill before it creates a branch, so the result is a working-tree diff. The last line makes the agent show what it read and a verdict for each pull request, which is what you check it against.

Once you move from named pull requests to a date window, list the same history yourself with the GitHub CLI:

gh pr list --state merged --base main --search "merged:>=2026-09-21" --limit 100 \
  --json number,title --jq '.[] | "#\(.number) \(.title)"'

Every number in that list should appear in the agent's report, with a verdict.

Inspect the proposed patch

In a test run of this example, the agent changed one page:

@@ -10,12 +10,12 @@ import { createClient } from "@acme/messages";

 const acme = createClient({
   apiKey: process.env.ACME_API_KEY,
-  retries: 3,
+  maxRetries: 3,
 });
 ```

 | Option | Default | Description |
 | --- | --- | --- |
 | `apiKey` | none | Your secret API key. Required. |
-| `timeout` | `10000` | Milliseconds before a request is aborted. |
-| `retries` | `2` | How many times to retry a failed request. |
+| `timeout` | `30000` | Milliseconds before a request is aborted. |
+| `maxRetries` | `2` | How many times to retry a failed request. |

Its report gave one verdict per pull request (shortened here):

| PR  | Verdict             | Details |
| --- | ------------------- | ------- |
| #41 | Docs updated        | The example used `retries: 3`, and the table listed `retries` and a `timeout` default of `10000`. The code now uses `maxRetries` (default `2`) and `timeout = 30_000`. |
| #42 | Skipped: unreleased | `sendBatch` only works when `flags.batchSend` is on, and it is `false` in `src/flags.ts`. |
| #43 | Already accurate    | Code moved, the backoff formula is unchanged, and the docs don't describe backoff timing. |

Review the patch the way you'd review a teammate's, against its evidence:

  • Every changed line should trace to a line in a merged diff. Here, maxRetries and 30000 both come from src/client.ts in #41. A value you can't find in any diff is a guess, so reject it.
  • Search for anything the patch missed. grep -rn "retries" docs/ should print nothing, since the old name is gone.
  • Check the skips. #42 is skipped because its flag is off, which is the boundary you set. If a skip reason is vague, ask the agent for the file and line.
  • Make the calls the agent leaves to you. Renaming an option breaks existing code. The agent noted that the repository has no upgrade page, so it added no upgrade note. Whether this release needs one is your call.

Verify and open the pull request

Run the same checks the skill runs:

npx blume build
npx blume validate --strict

blume build fails on invalid frontmatter and duplicate routes. blume validate --strict fails on any broken internal link, anchor, or asset (see Validate). When both pass and the patch is right, tell the agent to deliver:

The edits are right. Deliver as the skill describes: commit only the docs edits on a blume/ branch, push it, and open a pull request with the per-PR verdicts from your report in the body.

The skill names the branch blume/docs-refresh-YYYY-MM-DD and titles the pull request blume: refresh docs for YYYY-MM-DD. Its body has five sections: sources checked, docs changed, verification run, skipped checks, and remaining risk. For this example, a good body reads like this:

## Sources checked

History: `gh pr view` and `gh pr diff` for #41, #42, and #43 (the scope set in the prompt).

| PR | Verdict |
| --- | --- |
| #41 Rename retries to maxRetries and raise the default timeout | Docs updated |
| #42 Add batch sending behind the batchSend flag | Skipped: `flags.batchSend` is `false` in `src/flags.ts` |
| #43 Move retry backoff into its own module | Already accurate: no user-facing change |

## Docs changed

- `docs/configuration.mdx`: `retries` is now `maxRetries` in the example and the options table, and the `timeout` default is `30000` (was `10000`). Source: `src/client.ts` in #41.

## Verification run

- `npx blume build`: passed
- `npx blume validate --strict`: passed

## Skipped checks

- None.

## Remaining risk

- Renaming `retries` breaks existing code. The docs have no upgrade page, so this PR adds no upgrade note.
- `messages.sendBatch()` needs docs once `batchSend` is on.

A reviewer can check every claim in that body without rerunning the agent: the history it read, the diff each edit cites, and the checks it ran.

Confirm a clean no-op

A skill that always finds something to change is noise. Merge the docs pull request, then run the skill again over a window that covers the same changes, this time without the dry-run line:

Use the blume-update-docs skill.

Scope: the pull requests merged into main since 2026-09-21.
Feature flags live in src/flags.ts. A change gated by a flag that is false there is unreleased: skip it and say why.

In your report, list the exact commands you used to read history, then every pull request in scope with one verdict: docs updated, already accurate, or skipped (with the reason).

In a test run of this example, the second run marked #41, #43, and the merged docs pull request as already accurate. It skipped #42 for its flag again, skipped the pull request that added the skill as not a product change, and made no edits. A no-op leaves nothing behind, which you can confirm:

git status --short
gh pr list --state open --json number,headRefName \
  --jq '.[] | select(.headRefName | startswith("blume/")) | "#\(.number) \(.headRefName)"'

Both should print nothing. If a run ever opens a pull request whose only changes are rewording, that's outside the skill's rules: close it and tighten the prompt instead of merging the churn.

Keep review and flags in human hands

The skill stops at opening a pull request, but a scheduled agent can do whatever its token allows. Put the boundaries in GitHub, not only in the prompt:

  • Add a ruleset on main with Require a pull request before merging and at least one required approval. Nothing the agent pushes then reaches main without a person approving it.
  • Add a CODEOWNERS entry for docs/ and require review from code owners in the same rule, so the people who own the docs review the docs pull requests.
  • Keep the flag rule in AGENTS.md current. When you turn a flag on, name that feature in the next run's scope so the agent documents it.

Schedule it in GitHub Actions

Automate only after a local run and a no-op both came out right. This section uses the Claude Code GitHub Action. The skills page lists other runners, like a Codex or Cursor automation or plain cron, and the prompt carries over to any of them.

Install the Claude GitHub App on the repository and add an ANTHROPIC_API_KEY repository secret. Running /install-github-app in Claude Code does both. Then add the workflow:

name: Docs drift

on:
  schedule:
    - cron: "0 7 * * 1"
  workflow_dispatch:

concurrency:
  group: docs-drift

jobs:
  update-docs:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    permissions:
      contents: write
      pull-requests: write
      id-token: write
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v7
        with:
          node-version: 24
      - run: npm ci
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          prompt: |
            Use the blume-update-docs skill.

            Scope: the pull requests merged into main in the last 7 days. List them with gh pr list --state merged --base main --limit 100 and a merged:>= date search.
            Feature flags live in src/flags.ts. A change gated by a flag that is false there is unreleased: skip it and say why.
            Verify with npx blume build and npx blume validate --strict.

            If docs need updates, open or update a blume/* pull request. Its body lists the command you used to read history and every pull request in scope with one verdict: docs updated, already accurate, or skipped (with the reason).
            If nothing needs updating, report the same list and don't create a branch, commit, or pull request.
          claude_args: |
            --max-turns 60
            --allowedTools "Skill,Edit,Write,Bash(git *),Bash(gh pr list *),Bash(gh pr view *),Bash(gh pr diff *),Bash(gh pr checkout *),Bash(gh pr create *),Bash(gh pr edit *),Bash(npx blume build *),Bash(npx blume validate *)"

What each part is for:

  • Triggers: Mondays at 07:00 UTC, plus manual runs through workflow_dispatch. Both need the workflow file on the default branch.
  • fetch-depth: 0: a full clone, so git log and git show reach every commit in the window. The default checkout has one commit.
  • --allowedTools: with a prompt, the action runs in automation mode, where Claude can't edit files, run shell commands, or load the skill until you allow it. This list covers reading history, editing pages, the two Blume checks, and opening or updating a pull request. It leaves out gh pr merge on purpose.
  • --max-turns and timeout-minutes: caps on how long one run can go.
  • No github_token input: the action then authenticates as the Claude GitHub App, which is what id-token: write is for. Pull requests it opens run your CI; ones opened with the default GITHUB_TOKEN don't.

When a run changes docs, the result is a blume/* pull request with the same body as your local one, and later runs update it while it's open instead of opening another. When nothing drifted, the report is in the run's log. The pricing page covers what running this costs.

Troubleshooting

The scheduled run doesn't start, or fails before Claude runs

GitHub only schedules workflows from the default branch, and in a public repository it disables them after 60 days without activity; re-enable the workflow from the Actions tab. GitHub attributes a scheduled run to a repository user, usually the one who last changed the cron line, and the action rejects bot actors. If a bot made that change, list it in the action's allowed_bots input.

The run log shows permission denials

A command that no --allowedTools rule matches is denied. Rules match the command as written, so Bash(npx blume build *) doesn't cover npm run build. Either add a rule for the form the agent used, or name the exact command in the prompt, as the workflow above does.

The report misses pull requests

gh pr list returns 30 results unless you pass --limit, and a shallow checkout hides older commits from git log. Keep --limit 100 in the prompt and fetch-depth: 0 in the checkout, then compare the report with your own gh pr list output.

The docs pull request has no CI checks

GitHub doesn't start workflows for events made with the default GITHUB_TOKEN. If you passed github_token: ${{ secrets.GITHUB_TOKEN }} to the action, remove it so the action uses the Claude GitHub App.

The build fails on the agent's edit

Blume's frontmatter schema is strict, so a key the agent invents fails blume build with BLUME_FRONTMATTER_INVALID, naming the page and the unrecognized key. The skill fixes failures its own edits caused and reports ones that were already there. If main was already failing the build, fix that first, or no run can pass verification.

blume build refuses to run locally

While blume dev runs, blume build refuses to touch its .blume/ runtime. Add --isolated, or set BLUME_RUNTIME_DIR=.blume-verify in the agent's shell (see verifying while the dev server runs).

Next step

Install the update-docs skill

Add it at the root of the repository that holds your code and docs, then run it on one pull request you know changed documented behavior.

npx skills add haydenbleasel/blume --skill blume-update-docs --agent claude-code
Read the skills reference

A step here not working for you? Report a broken step.

Keep going.More guides.

Upgrade your docs with Blume.

Install today and ship a production-grade docs site in minutes. Free and open source, forever.

npx blume init