Most of the contract work I do for clients ends the same way: a PDF goes out, and a PDF comes back covered in comments. Highlights with no note attached. A sticky note that just says “fix this?” on page 9. Three people commenting on three different versions, because the file in the email thread is never the file in the shared drive.
The tools were never the problem. The problem was that “review” had no rules, so every round produced a new pile of comments I had to archaeology my way through. Here’s the loop I run now. It’s half conventions, half a small script, and it cut my review rounds from four-ish to usually two.
Rule 1: One marked copy, named like it means it
There is exactly one file that accepts comments: contract_CLIENT_review.pdf. Not v7, not final-FINAL, not the one attached to the third reply in the thread. When a new round starts, I archive the previous marked copy as contract_CLIENT_review_r1.pdf and the working name stays the same.
This sounds trivial and it eliminated the single biggest time sink: merging comments from parallel versions. If someone sends me comments on a different file, those comments don’t exist until they’re redone on the one marked copy. People adapt to this rule in about one round, because redoing comments is more annoying than following it.
Rule 2: Three annotation types, three meanings
I ask reviewers to use only three things, each with a fixed meaning:
- Highlight + note = a question. (“Is this the rate we agreed in March?”) Questions get answered in a reply note, not by silently editing the text.
- Strikethrough with replacement text = a proposed edit. If you want a clause changed, strike it and type the replacement. No “maybe soften this paragraph somehow” — that’s a question, use a highlight.
- Sticky note starting with DECISION: = something already settled that I should just apply. (“DECISION: payment terms are net 30, confirmed with their finance lead.”)
Every other annotation type — the arrows, the drawing tools, the lone carets with nothing attached — gets ignored, and reviewers know it gets ignored. The constraint feels strict for about ten minutes. Then people realize their comments actually get read, in order, and addressed.
Most of my clients annotate in whatever’s open on their machine. The ones without Acrobat use the annotate tool in PDFLean in a browser tab, which covers exactly these three types and nothing else — honestly the limited palette is part of why it works. A reviewer with fourteen annotation tools will use all fourteen.
Rule 3: The script turns comments into a checklist
Before I touch the document, I extract every annotation into a plain list with page numbers:
import fitz # PyMuPDF
def dump_annots(path):
doc = fitz.open(path)
for page in doc:
for a in page.annots():
info = a.info
yield {
"page": page.number + 1,
"type": a.type[1], # Highlight, StrikeOut, FreeText, ...
"author": info.get("title", ""),
"note": (info.get("content") or "").strip(),
}
for row in dump_annots("contract_CLIENT_review.pdf"):
print(f'p{row["page"]:>2} [{row["type"]:<9}] {row["author"]}: {row["note"]}')
That printout becomes the round’s checklist. Every line gets one of three marks as I work: applied, answered, or pushed back with a reason. At the end of the round, the marked-up checklist goes back to the client with the new draft. Half the value is the checklist itself — reviewers can see their DECISION notes were applied and their questions got answers instead of vanishing into a fresh PDF. The other half is selfish: two rounds later, when someone asks “why does clause 6 say net 30?”, the answer is in a text file, not buried in a dead PDF’s comment layer.
One gotcha: some tools store the visible note text in the annotation’s popup rather than its content field, so occasionally the script prints an empty note for an annotation that visibly has text. When the checklist shows a blank, I open that page and read the popup directly. It happens rarely enough that I haven’t bothered automating around it.
Rule 4: What ships is flat and clean
When a round closes, two files leave my machine:
- The new working draft (still commentable, still the same filename pattern).
- When the document is final: a flattened copy. Flattening bakes the annotations into the page content — or, for the truly final version, I produce a clean copy with no annotations at all and archive the last marked copy with its checklist.
The distinction matters. A flattened file is for records and signatures: nobody’s comment layer should be editable in the archived version of a signed agreement. The marked copy plus checklist is the audit trail; the flat file is the artifact. Mixing those two jobs into one file is how you end up, months later, unable to prove which comments were ever addressed.
What this doesn’t fix
It doesn’t make reviewers respond faster, and it doesn’t rescue a document that was badly structured before review started — comments cluster on the confusing parts either way, which is its own kind of useful signal. What it does is make each round terminal: every comment enters as a question, an edit, or a decision, gets extracted into a list, and leaves a written trace. Review stops being a recurring meeting with a PDF and becomes a queue that empties.
If your current process is “send PDF, receive vibes,” steal the parts in order: one marked copy first, the three-type rule second, the extraction script third. The first one alone is worth more than the other two combined.
Top comments (1)
The returned checklist is the useful part of this loop. I'd add an immutable round ID or PDF hash plus the annotation ID and position to each row: a page number, author and note alone won't distinguish two identical comments on one page, and the reused filename doesn't identify which text they referred to. A fixture could put the same note on two clauses, then insert a page in the next draft. The checklist should still trace each applied/answered decision back to the exact marked copy, without suggesting the old page number identifies the new clause.