OpenAI Codex for Documentation Generation
Why documentation always loses the priority fight
Every engineering team says documentation matters, and almost every engineering team ships without it. It's not because developers don't care — it's because documentation competes with shipping features, and shipping features always wins the sprint planning argument. By the time someone circles back to write the README, the function signatures have changed twice, the edge cases have multiplied, and nobody remembers why a particular workaround exists.
OpenAI Codex changes the economics of this problem. Instead of documentation being a separate task that requires a developer to stop coding, re-read their own code, and translate it into prose, Codex can read the code itself and generate a first draft in seconds. That draft still needs a human review pass, but the starting point is no longer a blank page — it's a working document grounded in the actual implementation.
This article is a practical walkthrough of using OpenAI Codex CLI for documentation generation: what it's good at, where it needs supervision, and how to build it into a workflow that keeps docs from rotting the moment they're written. If you want a structured, hands-on path through this and the rest of the Codex CLI toolchain, our OpenAI Codex CLI Tutorial course covers it end to end.
What "documentation generation" actually means with Codex
Before going further, it helps to separate documentation into the categories Codex handles differently, because the quality and required oversight vary a lot between them.
- API reference docs — function signatures, parameters, return types, exceptions. Codex is strongest here because this information is mechanically derivable from the code and type annotations.
- README and setup guides — install steps, environment variables, how to run the project locally. Codex does well when it can inspect
package.json,Dockerfile, CI config, and existing scripts. - Architecture and design docs — how modules interact, why a particular pattern was chosen. Codex can describe the "what" reliably but often guesses at the "why" unless you supply that context.
- Inline code comments and docstrings — line-level explanations embedded in the source. This is Codex's bread and butter and the lowest-risk use case.
- Changelogs and migration guides — summarizing diffs between versions in human-readable form. Codex is good at this when given a git diff or commit range to work from.
Knowing which bucket you're asking for changes how much you should trust the first output versus how much editing you should budget for.
Assuming you already have the Codex CLI installed and authenticated, running it against a project for documentation purposes starts the same way as any other Codex session — from inside the repository you want documented.
cd my-project
codexFrom the interactive session, you can scope a documentation request narrowly or broadly. A narrow request targets one file or module:
Generate JSDoc comments for every exported function in src/utils/validation.js.
Keep the existing code untouched — only add comments above each function.A broad request targets the whole repository:
Read through the entire src/ directory and produce a docs/ARCHITECTURE.md file
describing the module structure, main data flow, and how the API layer talks
to the database layer.The narrow request is safer and faster to review. The broad request is more impressive in a demo but requires more careful reading afterward, because Codex is synthesizing across many files and can occasionally misattribute responsibility between modules if the codebase has unusual naming conventions.
For non-interactive, scriptable documentation generation — useful in CI or as a one-off command — you can invoke Codex directly with a prompt:
codex exec "Generate a README.md for this repository. Include: project purpose,
installation steps based on package.json, available npm scripts, and a
brief description of the folder structure. Write it to README.md."The exec subcommand runs Codex non-interactively, which is what you want when calling it from a Makefile target, a pre-release script, or a scheduled job rather than a live terminal session.
It's also worth deciding upfront where Codex is allowed to run. For documentation work, a sandbox profile that grants read access to the whole repo but restricts writes to a docs/ directory (or specific files you're actively editing) keeps the blast radius small. A rough equivalent in a config file:
[sandbox]
mode = "workspace-write"
network_access = false
[approvals]
mode = "on-request"Disabling network access is deliberate — you want Codex describing what your code actually does, not blending in outdated blog posts or Stack Overflow answers it half-remembers about a library with a similar name.
Generating docstrings and inline comments
The most reliable, lowest-effort win is asking Codex to fill in docstrings for functions that don't have them. This is a good first exercise because the blast radius is small — you're adding comments, not changing logic — and it's easy to verify correctness by eye.
Here's a before-and-after example. Suppose you have this undocumented Python function:
def calculate_discount(price, customer_tier, quantity):
if customer_tier == "gold" and quantity >= 10:
return price * 0.75
elif customer_tier == "gold":
return price * 0.85
elif quantity >= 10:
return price * 0.9
return priceAsking Codex to "add a Google-style docstring to this function explaining the discount logic" typically produces something like:
def calculate_discount(price, customer_tier, quantity):
"""Calculate the discounted price for an order.
Applies a tiered discount based on customer loyalty tier and order
quantity. Gold-tier customers receive a larger discount, and bulk
orders (10 or more units) receive an additional reduction that stacks
with the gold-tier discount.
Args:
price (float): The original unit price before any discount.
customer_tier (str): The customer's loyalty tier, e.g. "gold".
quantity (int): The number of units in the order.
Returns:
float: The discounted price. Returns 0.75x price for gold-tier
bulk orders, 0.85x for gold-tier orders under 10 units,
0.9x for non-gold bulk orders, and full price otherwise.
"""
if customer_tier == "gold" and quantity >= 10:
return price * 0.75
elif customer_tier == "gold":
return price * 0.85
elif quantity >= 10:
return price * 0.9
return priceNotice that Codex correctly reverse-engineered the branching logic into prose, including the stacking behavior between the gold-tier and bulk-order discounts. This is exactly the kind of tedious-but-mechanical writing task that used to eat an afternoon when doing a documentation sprint across a legacy codebase.
The catch: Codex doesn't know if customer_tier is expected to ever be something other than a plain string like "gold", "silver", or "standard" unless that's defined somewhere it can see (an enum, a type definition, a comment). If your codebase relies on tribal knowledge that isn't encoded anywhere, Codex will document only what it can infer, and it will occasionally state assumptions as facts. Always read the generated docstring against the actual business rule, not just the code.
Generating a README from scratch
New projects, or old projects that never got a README, are a strong use case. Codex can inspect the dependency manifest, entry points, and folder layout to produce a reasonably complete first draft.
A useful pattern is to be explicit about the sections you want, rather than letting Codex decide the structure:
Create a README.md with these sections in this order:
1. Project title and one-line description
2. Prerequisites (node version, any required services)
3. Installation steps
4. Environment variables (check .env.example if it exists)
5. How to run the dev server
6. How to run tests
7. Project structure overview
8. Contributing guidelines (keep this brief, 3-4 sentences)Being this specific matters more than it seems like it should. Left unconstrained, Codex tends to produce README files that are stylistically fine but structurally generic — lots of emoji headers and a "Features" section that restates the obvious. Giving it a section list anchored in things it can actually verify (the .env.example file, the scripts block in package.json) keeps the output grounded instead of decorative.
Here's a realistic example of the environment variables section Codex produces when it's told to check .env.example:
## Environment Variables
Copy `.env.example` to `.env` and fill in the following values:
- `DATABASE_URL` — PostgreSQL connection string
- `JWT_SECRET` — secret key used to sign authentication tokens
- `STRIPE_SECRET_KEY` — Stripe API key for payment processing
- `RESEND_API_KEY` — API key for transactional email deliveryThis is directly derived from file contents, not invented, which is why grounding the prompt in real files rather than asking Codex to "write a README" cold produces a meaningfully better result.
Documenting APIs and generating reference docs
For teams maintaining a REST or internal API, Codex can walk through route handlers and produce endpoint documentation. This works best when your routes already follow a consistent pattern, since Codex is pattern-matching across the codebase to infer structure.
Given an Express route file like this:
router.post('/api/courses/:courseId/enroll', authenticate, async (req, res) => {
const { courseId } = req.params;
const { userId } = req.user;
const existing = await Enrollment.findOne({ userId, courseId });
if (existing) {
return res.status(409).json({ error: 'Already enrolled' });
}
const enrollment = await Enrollment.create({ userId, courseId, status: 'active' });
res.status(201).json(enrollment);
});A prompt like "document this endpoint including method, path, auth requirements, request params, and possible responses" produces something close to:
## POST /api/courses/:courseId/enroll
Enrolls the authenticated user in a course.
**Authentication:** Required (via `authenticate` middleware)
**Path Parameters:**
- `courseId` (string) — the ID of the course to enroll in
**Responses:**
- `201 Created` — enrollment successful, returns the enrollment object
- `409 Conflict` — user is already enrolled in this courseThis is accurate because the logic is small and self-contained. As route handlers grow more complex — nested validation middleware, shared error handlers, conditional response shapes based on feature flags — Codex's accuracy on edge-case responses drops, and you should treat the generated response codes as a checklist to verify against the code rather than a guaranteed-correct spec. A good habit is to ask Codex to cite the line numbers or middleware names it used to derive each claim, which makes the review pass faster.
Scaling this beyond a handful of endpoints is where Codex's ability to work across many files at once actually pays off. Instead of documenting one route at a time, you can point it at an entire routes directory and ask it to produce a single consolidated reference:
Walk through every file in src/routes/. For each exported route handler,
document: HTTP method, path, required auth, request body shape, query
parameters, and every distinct response status code the handler can
return. Group the output by resource (courses, users, payments) rather
than by file name. Note any handler where error responses are generated
by a shared middleware rather than the handler itself.That last instruction matters in real Express or Fastify codebases, where a generic error handler often catches thrown exceptions and maps them to status codes far away from the route definition itself. Without being told to look for that pattern, Codex will sometimes document only the success path because that's the only response it can see directly in the handler body — asking it to trace shared middleware explicitly closes that gap.
Keeping docs in sync with code changes
The harder, more valuable problem than generating docs once is keeping them from going stale. This is where Codex earns its keep long-term, because you can point it at a diff instead of a whole file.
A common workflow is to generate documentation updates as part of a pull request, using the diff as the input:
git diff main --stat
codex exec "Here is a git diff of changes about to be merged. Update
docs/API.md to reflect any new, removed, or changed endpoints. Do not
rewrite sections unrelated to this diff." < changes.diffThis constrains Codex to the surface area that actually changed, which reduces the risk of it silently rewording unrelated sections and introducing subtle drift between the doc's tone and the rest of the file.
Some teams wire this into a pre-merge CI check that flags (but doesn't block) PRs where source files changed but the corresponding doc file wasn't touched:
# .github/workflows/docs-check.yml
name: docs-drift-check
on: pull_request
jobs:
check-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check if API changed without docs update
run: |
if git diff --name-only origin/main | grep -q '^src/routes/'; then
if ! git diff --name-only origin/main | grep -q '^docs/API.md'; then
echo "::warning::Routes changed but docs/API.md was not updated"
fi
fiThis doesn't force anyone to run Codex, but it makes staleness visible instead of silent, which is often the missing ingredient in teams that "meant to" keep docs updated.
Documenting legacy code with unclear intent
The hardest documentation task isn't writing docs for new code — it's writing docs for old code where the original author is gone and the business logic looks arbitrary. Codex can still help here, but the technique changes: instead of asking it to explain the code's purpose (which it can't know), ask it to precisely describe the code's behavior.
For a gnarly, undocumented function full of magic numbers and nested conditionals, a more productive prompt is:
Do not guess at business intent. Describe exactly what this function does,
step by step, in terms of inputs and outputs. Flag any magic numbers or
unexplained constants as [NEEDS CONTEXT] rather than inventing a reason
for them.This produces a behavioral specification rather than a false narrative. It's less satisfying to read than a confident explanation, but it's honest about the limits of what can be inferred from code alone — and it gives whoever maintains that function next a clear, flagged list of the exact questions to go ask around the team, rather than a document that quietly asserts wrong assumptions as if they were established facts.
Common mistakes teams make with AI-generated docs
A few patterns show up repeatedly once teams start relying on Codex for documentation.
- Trusting generated docs without spot-checking against the code. The most common failure mode is treating Codex output as final rather than as a first draft. Even accurate-sounding docstrings should be checked against actual behavior, especially around edge cases and error handling.
- Asking for documentation with no scope boundary. "Document this codebase" produces a shallow pass over everything. Scoping to a module, a diff, or a specific file produces much higher-quality output because Codex isn't spreading its attention across an entire repo.
- Skipping the "why" entirely. Codex documents what code does well but can't explain why a workaround exists unless that context is in a commit message, ticket reference, or comment somewhere it can read. If that context matters, feed it to Codex explicitly in the prompt.
- Not regenerating docs after refactors. Documentation generated once and never touched again decays exactly as fast as hand-written docs. The advantage of Codex is that regenerating is cheap, so there's less excuse not to do it after significant changes.
- Letting Codex invent example values. When generating usage examples, Codex sometimes fabricates plausible-looking sample data (fake API keys, made-up user IDs) that can be mistaken for real. Always mark example blocks clearly and review them for anything that looks like it could be copy-pasted as real credentials.
- Applying one prompt template to every doc type. A prompt tuned for docstrings ("be terse, describe args and returns") produces flat, unhelpful architecture docs when reused as-is, because architecture docs need narrative flow between sections rather than a list of fields. Keep a small library of prompt templates per doc type rather than reusing whatever worked last time.
- Forgetting to tell Codex what "done" looks like. Open-ended prompts like "improve the docs" invite Codex to keep expanding scope — adding sections nobody asked for, rewording things that were already fine. Give it a concrete definition of done: which files, which sections, what to leave alone.
None of these are reasons to avoid the tool. They're the same review discipline you'd apply to a junior engineer's first documentation pass — thorough, well-organized, occasionally confidently wrong about something only a team veteran would catch.
Building this into a real workflow
Treat Codex-generated documentation as a step in your existing process, not a replacement for review. A workflow that holds up over time typically looks like this:
- Write or change code as usual.
- Before opening a PR, run a scoped Codex prompt against the changed files to draft or update docstrings and relevant doc sections.
- Read the generated docs against the code — not against your memory of the code — checking especially for edge cases and error paths.
- Correct anything Codex got wrong or flagged as
[NEEDS CONTEXT], filling in the real answer from your own knowledge. - Commit the docs alongside the code change in the same PR, so reviewers see both together.
- Periodically (monthly, or after a major refactor) run a broader Codex pass over architecture-level docs to catch drift that accumulated across many small PRs.
Beyond the individual-PR loop, a few team-level habits make this durable rather than a one-time novelty:
- Store your prompt templates in the repo, not in someone's notes app. A
docs/codex-prompts/folder with a prompt per doc type (docstrings, README, API reference, architecture) means every engineer gets the same quality of output, and the prompts themselves become reviewable, versioned artifacts. - Assign doc review the same weight as code review. If a PR changes public behavior and the docstring update reads like it was rubber-stamped, treat that the same way you'd treat an untested code change — send it back.
- Use Codex to audit before a release, not just to generate during development. A pre-release pass that asks Codex to compare
docs/API.mdagainst the actual route definitions catches drift that accumulated silently across a dozen small PRs, each of which individually looked fine. - Don't regenerate docs you haven't changed just because you can. Regenerating a stable, accurate doc for no reason introduces unnecessary diff noise and risks Codex rewording something that was already correct into something subtly less precise.
The point isn't to remove humans from documentation — it's to remove the blank-page problem, which is the actual reason documentation gets skipped in the first place. Once there's a draft grounded in real code, correcting it takes minutes instead of the hour it takes to write from nothing. Over a year, that difference compounds into the gap between a codebase with docs that roughly track reality and one where the wiki is a museum piece nobody trusts enough to open.
Getting hands-on with Codex CLI
Reading about a workflow and actually building the muscle memory for it are different things. The fastest way to get comfortable using Codex for documentation — and the rest of what the CLI can do, from refactoring to test generation — is to work through structured, practical exercises rather than piecing it together from trial and error.
Our OpenAI Codex CLI Tutorial course walks through exactly this: setting up Codex CLI, scoping prompts effectively, generating and reviewing documentation across real codebases, and building it into a CI-aware workflow so your docs stop falling out of sync with your code. If documentation has always been the thing your team means to get to eventually, this is a concrete way to actually close that gap.
AI CodingShip full-stack AI apps at conversation speed — specs, agents, deploys, all from the terminal.
CodexLearn to drive OpenAI's coding agent: real tasks, safe sandboxing, and terminal-to-cloud workflows that ship.