PDF extraction

Claude vs Codex: How Two Agents Read PDFs by Default

Anthropic's and OpenAI's official PDF skills read side by side — text-first vs render-and-look, and what each one knows about forms.

August 19, 2026 11 min read okraPDF

Anthropic and OpenAI both ship an official PDF skill for their coding agent. Both reach for the same three Python libraries — pypdf, pdfplumber, reportlab. Both were written by people who have clearly been burned by real documents.

And they give opposite first instructions.

Anthropic’s skill opens by extracting text. OpenAI’s opens by rendering the page to a PNG and looking at it — and then tells the model not to trust text extraction for layout. That is not a style difference. It falls directly out of what each agent can do with a PDF before any skill is loaded, and it changes what happens when you drop a document in front of either one.

Here is what each skill actually says, why they diverge, and what is worth stealing if you are writing a PDF skill of your own.

Where the two skills live

Anthropic’s is public. It lives in anthropics/skills under skills/pdf/, and it is three documents plus a script directory:

skills/pdf/
├── SKILL.md        # 314 lines — "PDF Processing Guide"
├── forms.md        # 294 lines — filling forms, step by step
├── reference.md    # 611 lines — pypdfium2, pdf-lib, troubleshooting
└── scripts/        # 8 Python scripts

The frontmatter description is a keyword net, written to fire on almost anything:

Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable.

Codex’s is not a repo you clone. It ships inside the Codex primary runtime as a versioned plugin, and on macOS you can read it off your own disk:

~/.codex/plugins/cache/openai-primary-runtime/pdf/<version>/skills/pdf/SKILL.md

It is one file, 148 lines, with no Python scripts at all — the only executable it carries is mark_artifact_operation_started.mjs, a runtime hook that flags “I am about to author an artifact.” The plugin.json next to it declares "author": OpenAI, "license": "MIT", a brand color, and a set of default prompts (“Review this PDF and verify its layout”). Its description is a capability sentence rather than a keyword net:

Read, create, inspect, render, and verify PDF files where visual layout matters, including fillable AcroForms.

That scoping — where visual layout matters — is the whole design in one clause.

The first instruction each skill gives

Anthropic’s SKILL.md, under “Quick Start”:

from pypdf import PdfReader, PdfWriter

reader = PdfReader("document.pdf")
print(f"Pages: {len(reader.pages)}")

text = ""
for page in reader.pages:
    text += page.extract_text()

Codex’s SKILL.md, under “Workflow”:

  1. Prefer visual review: render PDF pages to PNGs and inspect them.
  2. Use reportlab to generate PDFs when creating new documents.
  3. Use pdfplumber or pypdf for text extraction and quick checks; do not rely on text extraction for layout fidelity.

Same libraries, inverted priority. Claude’s skill starts by reading the text. Codex’s starts by looking at the page and explicitly demoting the text to “quick checks.”

Why the defaults differ: look one layer below the skill

The skills disagree because the agents underneath them have different senses.

Claude can already open a PDF. In Claude Code, the Read tool takes PDFs directly, with a pages parameter for page ranges. At the API level it goes further: the Messages API accepts a PDF as a document content block (base64, up to 32 MB and 600 pages — 100 pages on 200K-context models), processed as both extracted text and page images, with page_location citations pointing back at the source page. Ingestion is solved a layer down, so the skill does not need to teach it. It can spend its 314 lines on library recipes instead: merge, split, rotate, watermark, encrypt, extract images, OCR.

Codex has no PDF input path. It can view an image — that is what its view_image tool is for — but a .pdf is opaque to it. So the skill’s real job is to manufacture something viewable:

pdftoppm -png "$INPUT_PDF" "$OUTPUT_PREFIX"

The tell is in the fallback clause: “If unavailable, install Poppler or ask the user to review the output locally.” No renderer, no eyes. That is why the skill also carries brew install poppler and apt-get install -y poppler-utils, and why “render, look, fix, re-render” is the loop rather than an optional QA step.

The practical consequence: “read this PDF and tell me what the total is” works on Claude with no skill installed at all. The same prompt on Codex without Poppler quietly degrades to text extraction — the exact mode its own skill tells you not to trust when layout matters.

Forms: same job, two philosophies

Filling a PDF form is where both skills stop being cookbooks and start being opinionated. They land in completely different places.

Anthropic: scripts, JSON handoffs, and a fixed order

forms.md opens with a shout:

CRITICAL: You MUST complete these steps in order. Do not skip ahead to writing code.

For a PDF that has real form fields, the procedure is:

  1. check_fillable_fields.py — branch on whether AcroForm fields exist at all.
  2. extract_form_field_info.py — dump every field to JSON: field_id, page, rect, type, plus checked_value/unchecked_value for checkboxes and a radio_options list for radio groups.
  3. convert_pdf_to_images.py — render the pages, and look at them to work out what each field is for, converting the PDF rects to image coordinates.
  4. Write a field_values.json mapping field_id → value.
  5. fill_fillable_fields.py — which validates your IDs and values and prints errors to correct.

For a PDF without form fields — a scan, or a flat design export — it gets more elaborate. extract_form_structure.py pulls every text label, horizontal rule, and small square with exact coordinates. If that yields real labels you use structure-based coordinates; if it is a bare scan you fall back to visual estimation, including a zoom-refinement pass where the model crops the page image around a field (magick images/page_1.png -crop 300x80+50+120 ...) to nail the entry box edges. Before anything is written, check_bounding_boxes.py rejects intersecting boxes and entry boxes too small for the requested font size. Then fill_pdf_form_with_annotations.py writes it, and you re-render to verify placement.

The design principle is visible through the whole file: keep the model out of the exact-match business. Field IDs, coordinate conversion, and overlap detection are handled by deterministic scripts. The model supplies judgment — which field means what — not arithmetic.

Codex: no scripts, but deep PDF object-model knowledge

Codex’s forms section is a third of the length and contains no tooling. It spends its budget on the thing that actually goes wrong, starting with a warning that inverts its own visual-first workflow:

Visual review alone is not a correctness check for a fillable PDF. A page /Widget annotation can render a value from its appearance stream while the canonical /AcroForm/Fields tree is missing or contains a stale value.

From there it is all PDF internals:

  • Enumerate both representations before filling — reader.get_fields() and every page’s /Annots, following /Parent and /Kids.
  • Do not call writer.reattach_fields() blindly: if a widget and a canonical field share a name but have no /Parent relationship, reattaching creates a second top-level field with the same name. Report the ambiguity instead.
  • Keep the result interactive by default; flatten=True only when the user asked for a static form, and never on a signed PDF without an explicit decision.
  • Know the library’s sharp edge: pypdf’s flatten=True paints appearances but does not remove the widgets, so you follow it with remove_annotations(subtypes="/Widget") and pop /AcroForm yourself.
  • Reopen the written file before delivery. Every expected field present with the expected /V; every widget’s effective value (its own or inherited from /Parent) in agreement; a non-empty /AP /N appearance on each updated widget. And explicitly: do not rely on /NeedAppearances or a successful PNG render as proof that logical field data was updated.

Note the symmetry. Anthropic’s skill defaults to text and forces you to look at images to fill a form. Codex’s skill defaults to images and forces you to inspect the object graph to fill a form. Both arrive at the same conclusion from opposite directions: for a form, one representation is never enough.

Side by side

Anthropic skills/pdfCodex pdf plugin
DistributionPublic repo, installed per project or userBundled in the Codex runtime, versioned
Size3 docs (~1,200 lines) + 8 scripts1 doc, 148 lines
Default for readingExtract text (pypdf, pdfplumber)Render to PNG and look
Creatingreportlabreportlab
Verification targetWhere the ink lands — validation images, bounding-box overlap checksBoth: re-render after every edit, then reopen and check field objects
Forms approachDeterministic scripts + JSON handoffsProse knowledge of AcroForm/widget semantics
OCR / scanspytesseract + pdf2image snippetNot mentioned
Workspace conventionsNonetmp/pdfs/, output/pdf/, artifact-start marker, citation format
Typography ruleNo Unicode sub/superscripts (ReportLab renders them as black boxes)ASCII hyphens only, no U+2011
LicenseProprietary (see LICENSE.txt)MIT per plugin.json

Those typography rows are worth a second look, because they are the same class of instruction — hard-won glyph rules that exist because someone shipped a PDF full of black squares — arrived at independently.

The conventions row is the other real difference. Codex’s skill is written for a product surface: where intermediates go, where finals go, when to mark an artifact operation as started, and exactly how to cite the resulting file in the final message (:codex-file-citation{path="..." purpose="output"}, inline, once per file). Anthropic’s skill is a library guide and leaves all of that to the host.

What both skills quietly assume

Read them together and the shared blind spots show up fast.

A local Python and a package installer. Codex says to prefer the bundled runtime, then falls back to uv pip install reportlab pdfplumber pypdf. Anthropic’s just imports. In a sandbox with no network or no write access to site-packages, both plans stop at line one.

Documents small enough to look at. Rendering pages into context is the most expensive way to read a document. It is the right call for a one-page invoice where layout is the content, and the wrong call for a 400-page filing. Neither skill has a page-routing strategy — no “extract text first, render only the pages where the text looks structurally suspicious.”

A text layer that exists. Anthropic gives OCR one snippet (pdf2image + pytesseract); Codex does not mention scans at all. Scanned documents are precisely where extract_text() returns an empty string and a naive agent reports that the page is blank.

One document at a time. Neither addresses a queue of five thousand invoices, retries, schema conformance across a batch, or the provenance a downstream agent needs to justify a number it extracted. That is not a criticism — a skill is a prompt, not a pipeline — but it marks the line where a skill’s job ends and a document API’s begins.

What to steal for your own PDF skill

If you are writing a skill for your own agent, these are the transferable moves:

  1. Name the verification target explicitly. Pixels, object graph, or both. Every genuinely useful instruction in both skills descends from that one choice. “Verify the output” without naming what you compare against is a wasted line.
  2. Ship a script for anything with an exact-match failure mode. Field IDs, coordinate conversion, and overlap checks are arithmetic. Anthropic’s check_bounding_boxes.py catches an error class — two entry boxes overlapping — that a model eyeballing a render will confidently miss.
  3. Put trap knowledge in prose. pypdf’s flatten=True leaving widgets behind is not something a model derives; it is something it has to be told. Three sentences about a library’s sharp edge outrank a page of generic advice.
  4. Match the trigger description to the scope. Anthropic’s keyword net fires on nearly any mention of a .pdf, which suits a general-purpose document skill. Codex’s “where visual layout matters” deliberately does not fire for “grep this PDF for a phone number.” Broad costs you tokens on irrelevant matches; narrow costs you the times it should have loaded and didn’t.
  5. Declare dependencies and the failure path. Codex names its bundled runtime, gives the uv/pip/brew/apt fallbacks, and ends with “tell the user which dependency is missing and how to install it locally.” That last sentence is what stops an agent from silently producing a worse answer.
  6. Let conventions travel with the skill. Temp directories, output directories, and citation format are not PDF knowledge, but they are the difference between an artifact the host can find and a file in /tmp nobody sees again.

The short version

Claude reads PDFs; Codex looks at them. Anthropic’s skill teaches libraries and hands the fiddly parts to scripts. OpenAI’s teaches a render-verify loop and hands the fiddly parts to the model, armed with unusually specific knowledge about how AcroForms lie.

Neither is wrong, and if you write your own skill the useful question is not which to copy — it is which sense your agent already has, and what the skill has to build to make up the difference.

If the document work you want the agent to do is extraction rather than authoring — tables, fields, provenance, a schema that holds across a batch — the skill should not be teaching the model to parse at all. It should teach it when to call something that already does, and what to do with the result. We wrote up that pattern for Claude Code and Cowork in Use okraPDF with Claude Cowork and Claude Skills, and the open-source Python side of the same problem in Extract Tables from PDF in Python.