Generate and maintain documentation with Codex
PLCodex generates API references, architecture documents, inline TSDoc, and changelog drafts by reading source files and git history, working in an isolated Git worktree so nothing reaches the main checkout unreviewed. Generation is the easy half. Documentation stays true when deterministic gates check it and a scheduled codex exec drift report returns schema-checked JSON that CI can fail on.
This page is for developers who own a service’s documentation and tech leads who want docs to stop being a review burden. The situation is familiar: docs/API.md still lists an endpoint removed two sprints ago, the README’s setup command fails on a clean machine, and every new joiner asks the same questions because ARCHITECTURE.md describes the system as it was before the last refactor. Nobody updates docs by hand, so the fix is a workflow that regenerates them and a gate that fails when they drift.
What this page gives you
Section titled “What this page gives you”- A decision table: which documents Codex should generate, which it should only draft, and the check that proves each one.
- An
AGENTS.mdsection and a repository skill that make every Codex session write docs the same way. - Copy-paste prompts for an API reference with a contract test, a cited
ARCHITECTURE.md, and a changelog draft. - A read-only drift report from
codex exec --output-schemathat turns “the docs look fine” into an exit code. - A failure-mode list with the recovery for each one.
Before you start, have Codex installed and an AGENTS.md at the repository root; setting up AGENTS.md for Codex covers the basics. Commands on this page were checked against Codex CLI 0.157.1.
Which documentation should Codex write?
Section titled “Which documentation should Codex write?”Codex is reliable wherever the answer is in the code and unreliable wherever the answer is in someone’s head. Decide per document type before you prompt:
| Document | Source of truth | Codex’s role | What proves it is right |
|---|---|---|---|
| TSDoc / JSDoc on exported symbols | Signatures, types, thrown errors | Generate | TypeDoc validation fails on any undocumented export |
API reference (openapi.yaml or API.md) | Route handlers, validation schemas, middleware | Generate | Redocly lint plus a contract test that compares documented routes with registered routes |
ARCHITECTURE.md | Directory layout, entry points, data flow | Generate with citations | A script checks that every cited path exists |
| README setup section | Scripts that actually run | Generate, then execute | CI runs the setup commands from a clean checkout |
| Changelog | git log between tags | Draft | The release owner edits the wording and signs off |
| Decision records (“why we chose X”) | People’s reasoning | Interview and draft only | The decision owner writes or approves every rationale |
The last row matters most. Asked why a module uses a queue, Codex writes a plausible reason whether or not one exists. Keep rationale with people, using the interview workflow in architecture decisions agents can follow.
Put the gates in the repository first
Section titled “Put the gates in the repository first”Add the checks before the first generated document, so the first diff is already verified. For a TypeScript service, install TypeDoc and the Redocly CLI (0.28.20 and 2.54.3 on npm on 2026-09-26):
npm install -D typedoc @redocly/cliThen add one docs:check script to package.json. --emit none validates without writing HTML, and --treatValidationWarningsAsErrors makes TypeDoc exit non-zero when an exported symbol under src/ has no documentation:
{ "scripts": { "docs:check": "typedoc --emit none --entryPointStrategy expand --entryPoints src --validation.notDocumented --treatValidationWarningsAsErrors && redocly lint docs/openapi.yaml && bash scripts/check-doc-citations.sh" }}The citation check catches the commonest form of invented prose: a claim that cites a file that does not exist. Save it as scripts/check-doc-citations.sh:
#!/usr/bin/env bash# Fail when a doc cites a repository path that does not exist.# A line suffix such as `src/routes/billing.ts:42` is checked as the bare path.set -uo pipefailmissing=0for doc in docs/ARCHITECTURE.md docs/API.md README.md; do [ -f "$doc" ] || continue for path in $(grep -oE '`(src|scripts|tests|docs|config)/[A-Za-z0-9_./-]+(:[0-9]+)?`' "$doc" \ | tr -d '`' | sed -E 's/:[0-9]+$//' | sort -u); do if [ ! -e "$path" ]; then echo "$doc cites a missing path: $path" missing=1 fi donedoneexit "$missing"The three gates are chained with &&, so npm run docs:check stops at the first one that fails, and redocly lint fails anyway until docs/openapi.yaml exists. To build the backlog, run them separately once and keep every failure:
npx typedoc --emit none --entryPointStrategy expand --entryPoints src --validation.notDocumented --treatValidationWarningsAsErrors; \ npx redocly lint docs/openapi.yaml; \ bash scripts/check-doc-citations.shThat list is the backlog you hand to Codex, one directory per task. The TypeDoc and README-execution gates are tool-independent; the Claude Code documentation guide shows a CI step that runs a README’s quick-start block in a clean container.
Teach Codex your documentation rules once
Section titled “Teach Codex your documentation rules once”Codex reads AGENTS.md before it does any work, so conventions written there apply to every session, including worktree threads and scheduled runs. Add a short section to the root AGENTS.md:
## Documentation- API reference: docs/openapi.yaml. Architecture: docs/ARCHITECTURE.md. Changelog: CHANGELOG.md.- Cite every claim about the code as a repo-relative path in backticks, for example `src/routes/billing.ts`.- Never write rationale ("we chose X because") that is not in a code comment, docs/adr/ or the prompt. Write "(rationale: ask the owner)" instead.- In hand-written docs, edit only between <!-- generated:start --> and <!-- generated:end -->.- Before finishing any change to docs/ or to an exported API, run `npm run docs:check` and fix failures.Keep it to rules that apply to every task. The multi-step procedure for a full documentation pass belongs in a skill, which Codex loads only when a task needs it. Save this as .agents/skills/docs-sync/SKILL.md:
---name: docs-syncdescription: Bring docs/ back in line with the code after a change. Use when a task changes routes, exported APIs, scripts or directory layout.---
1. Run `git diff --name-only origin/main...HEAD` and list the changed routes, exports and scripts.2. For each change, find every place in docs/, README.md and docs/openapi.yaml that describes it.3. Update only those passages. Cite the file each claim comes from.4. Run `npm run docs:check`. Fix documentation, never code, until it passes.5. Report: each doc changed, the code change that caused it, and the final docs:check output.Invoke it with $docs-sync in a prompt, or browse installed skills with /skills. If the skill does not appear, optimizing AGENTS.md and skills for Codex shows how to see exactly what Codex loaded.
Generate reference docs in a worktree task
Section titled “Generate reference docs in a worktree task”A worktree keeps generated files away from your checkout until you have checked them, and lets a docs task run beside your feature work.
-
Start the task in its own worktree. Commit or stash first: a worktree starts from committed state, so uncommitted edits are not in it.
Terminal window codex --worktree--worktreeruns the session in a new managed Git worktree (worktree support is on by default since Codex CLI 0.156.0).Start a new thread in the project and choose Worktree as where it runs. The app is available on macOS and Windows;
codex appopens it from a terminal. -
Install dependencies inside the worktree (
npm ci), or the gates fail for a reason that has nothing to do with the docs. -
Paste one prompt below, one document or one directory per task. Small tasks give diffs you can spot-check in a minute.
-
Let the gates decide. The prompt ends by running
npm run docs:check. Green means every export is documented, the spec parses and every citation resolves. It does not mean the prose is true, which is why the contract test exists. -
Run a review pass on the diff. Type
/reviewin the session, or runcodex exec review --uncommittedfor the built-in review. To apply the documentation-review instructions below, run them as a read-onlycodex exec(see the review section). -
Bring the work home through Git. Commit in the worktree and open a pull request. Isolated Codex tasks with Git worktrees covers merging back and cleaning up.
From then on the contract test is the oracle: a route added without a spec entry turns CI red, whoever added it. The rest of the contract-first API workflow is in building APIs with Codex.
The “Drift found” list is often worth more than the document: it is the audit of the docs you already have. Check every (inferred) marker; each one is a claim no file backs.
For TSDoc, point Codex at the TypeDoc warnings for one directory and add two constraints: “follow the style already used in src/lib/database.ts” and “keep each comment shorter than the function body it documents”. The second curbs the most common failure of generated comments, which is restating the code.
Draft a changelog from git history
Section titled “Draft a changelog from git history”A changelog is a communication to users, so Codex drafts it and a person owns the wording. Run this in a normal local thread; it only reads history and writes one file.
The “Not user-facing” list makes the agent’s classification checkable. The release owner reads that list and the breaking changes, not every entry.
Catch documentation drift with codex exec
Section titled “Catch documentation drift with codex exec”Generated docs are true on the day they are generated. The drift report is what keeps them true: a read-only codex exec run that compares docs with code and returns JSON in a fixed shape.
Save the schema as scripts/docs-drift/schema.json. --output-schema runs in strict mode, so every object lists all its properties in required and sets additionalProperties to false:
{ "type": "object", "properties": { "verdict": { "type": "string", "enum": ["current", "drift"] }, "findings": { "type": "array", "items": { "type": "object", "properties": { "doc": { "type": "string" }, "claim": { "type": "string" }, "code_evidence": { "type": "string" }, "kind": { "type": "string", "enum": ["missing", "stale", "wrong", "dead-reference"] } }, "required": ["doc", "claim", "code_evidence", "kind"], "additionalProperties": false } } }, "required": ["verdict", "findings"], "additionalProperties": false}Save the prompt as scripts/docs-drift/prompt.md:
Run it from the repository root and let jq -e turn the answer into an exit code:
codex exec -c default_permissions=":read-only" \ --output-schema scripts/docs-drift/schema.json -o drift.json \ "$(cat scripts/docs-drift/prompt.md)"jq -e '.verdict == "current"' drift.json > /dev/null || { jq -r '.findings[] | "\(.doc): \(.claim) <- \(.code_evidence)"' drift.json exit 1}:read-only is a built-in permission profile (beta since Codex CLI 0.138.0); the legacy equivalent is --sandbox read-only. Use one or the other, not both, because OpenAI states that the two systems do not compose. The schema guarantees the shape of the answer, not its truth: the code_evidence field is there so you check a finding by opening one file, not by rereading the docs.
Where should the drift check run?
Section titled “Where should the drift check run?”Create an automation in the Codex desktop app with the prompt above, pick the project, and schedule it weekly. It runs only while your machine is awake and reports to the app’s inbox. Test the prompt twice by hand before you schedule it; the scheduled Codex automations guide covers the procedure and sandbox choice.
Run the same codex exec command from a scheduled GitHub Actions job on the default branch, never on a contributor’s branch while an API key is in the environment. The hardened setup, with openai/codex-action@v1, least-privilege permissions and the key kept out of the agent’s shell, is in CI/CD with the Codex GitHub Action. Post drift.json as a job summary or open an issue from it.
When the report shows drift, start a worktree task with $docs-sync and paste the findings into the prompt. The fix then goes through the same gates as any other docs change.
Review a docs diff without reading every line
Section titled “Review a docs diff without reading every line”Documentation diffs are long and look plausible, which makes them the worst kind of diff to read line by line. Put the reading on the gates and a focused review pass, and keep human attention for the claims that no gate can check.
Save it as scripts/docs-drift/review.md and run it as a read-only codex exec, which reads the diff the prompt names:
codex exec -c default_permissions=":read-only" "$(cat scripts/docs-drift/review.md)"For the built-in review without custom instructions, run codex exec review --base main. In Codex CLI 0.157.1, --base, --uncommitted and --commit cannot be combined with custom instructions; the CLI exits with “cannot be used with ‘[PROMPT]’”.
Who signs off stays explicit:
| Change | Checked by | Signs off |
|---|---|---|
| TSDoc, OpenAPI, citations | npm run docs:check, contract test | Code owner of the touched paths, from the gate results |
ARCHITECTURE.md | Citation check, (inferred) markers, review pass | Tech lead reads the “Drift found” list and every (inferred) line |
| Changelog | Human | Release owner |
| Rationale and decision records | Human | Decision owner |
For the evidence a pull request should carry so the reviewer can approve from it, see the evidence bundle.
When Codex-generated documentation goes wrong
Section titled “When Codex-generated documentation goes wrong”The docs explain why, and the reasons are invented. Codex fills gaps with plausible rationale. Recovery: the AGENTS.md rule above makes it write “(rationale: ask the owner)” instead; search the diff for “because” and “in order to” and confirm each one has a source.
Codex rewrites hand-written sections. A full-file regeneration flattens the paragraphs a person wrote. Recovery: wrap generated parts in <!-- generated:start --> and <!-- generated:end -->, state the rule in AGENTS.md, and add a check that fails when a diff touches lines outside the markers.
The gates fail in the worktree for reasons unrelated to the docs. A fresh worktree has no node_modules and none of your gitignored local files. Recovery: run npm ci first, or add the install to the worktree setup described in the worktrees guide.
Codex ignores your documentation rules. Since Codex CLI 0.150.0, untrusted projects no longer supply project-level AGENTS.md instructions. Recovery: trust the project when Codex asks, then confirm the rules loaded with codex debug prompt-input.
The drift report changes every week without any code change. A vague prompt produces a different list on each run. Recovery: run it twice by hand; if the findings differ, narrow the scope (one doc per check) and require code_evidence for every finding, which the schema already enforces.
The drift job “fixes” the docs itself. A scheduled job that writes is a job whose changes nobody asked for. Recovery: keep the report on :read-only, and make fixing a separate worktree task that goes through review.
File citations break after a move. Renaming src/services/ to src/domain/ silently invalidates every citation. Recovery: the citation check fails the build; point Codex at its output with “update the citations in docs/ to the new paths, do not change code”. The citation check does not follow relative Markdown links such as [setup](../setup.md); those need a link checker in CI, for example markdown-link-check (3.15.0 on npm): find docs -name '*.md' -print0 | xargs -0 -n1 npx markdown-link-check, with redocly lint covering references inside the OpenAPI spec.
Where to go next with documentation in Codex
Section titled “Where to go next with documentation in Codex”- Building APIs with Codex: the contract-first workflow the API reference documents.
- Scheduled Codex automations: schedule the drift report and other recurring checks.
- Running Codex non-interactively with
codex exec: the full flag set, JSONL events and CI authentication. - Documentation as context for AI agents: writing docs the agent itself reads.
- For third-party library docs, connect Context7 with
codex mcp add context7 -- npx -y @upstash/context7-mcp, so generated examples use current APIs; see MCP setup for Codex. - Using another tool? The same workflow for Claude Code and for Cursor.