-U0, -U3 or --function-context: Count the Tokens Your Review Agent Reads per Diff
Book: LLM Observability Pocket Guide Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with
- Book: LLM Observability Pocket Guide
- Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
Your review agent starts from whatever git diff printed, and you picked that format, maybe without noticing. Same commit, different flags, a different bill and a different picture of your code.
By default git gives each change three lines of context above and below. A 2025 paper on LLM code review, LAURA, put the problem in one line: "since diffs by default only include three lines of context before and after, code context is often missing or insufficient." Its fix was to widen each diff to the enclosing function, capped at three times the original diff length.
That fix costs tokens. So does every other option, and you can count exactly how many.
Seven ways to print one commit
All of these are standard git diff options, and they all work on git show too:
-
-U0prints the changed lines and the@@hunk header, with zero context. -
-U3is the default. You get it whenever nobody passes a flag anddiff.contextis unset. -
-U10is a wider fixed window, ten lines each side. -
--function-context(-W) shows "whole function as context lines for each change", with function boundaries found the same way git finds hunk headers. -
--word-diff=plainmarks changes inline as[-removed-]and{+added+}, so an edited line appears once instead of twice. It still carries the usual context lines. -
--staton its own gives file names and line counts only, no code. It is the floor, the cheapest thing you can send. -
--statplus the full post-change files gives file names and line counts, then every changed file in full.
One detail makes -U0 less blind than it looks. Each hunk header ends with text git picks from the code above the change. By the gitattributes rules, that text is "a line that begins with an alphabet, an underscore or a dollar sign" unless a diff driver for the language says otherwise. So the model at -U0 usually still learns which function it is in. It just doesn't see anything in that function except the changed lines.
And --stat plus full files has the opposite gap. The model gets the whole file after the change and no removed lines at all. It has to work out what changed by itself.

Same change, wider frames: every extra ring is context the model reads and you pay for.
The script
Run it inside any repository. It takes the last N non-merge commits (five by default), renders each in every format, and prints a table. If ANTHROPIC_API_KEY is set it uses Anthropic's token counting endpoint, which is free to call within your tier's requests-per-minute limit. Without a key it counts offline with tiktoken's o200k_base encoding.
#!/usr/bin/env python3
"""Tokens per diff format for the last N commits.
Usage: python3 diff_tokens.py [N] (run inside a git repo)
Counts with Anthropic's count_tokens if ANTHROPIC_API_KEY is
set, else offline with tiktoken's o200k_base.
"""
import os
import subprocess
import sys
FORMATS = {
"U0": ["-U0"],
"U3": ["-U3"],
"U10": ["-U10"],
"W": ["--function-context"],
"word": ["--word-diff=plain"],
"stat": ["--stat"],
}
def git(*args):
return subprocess.run(
["git", *args], check=True, capture_output=True,
text=True, errors="replace",
).stdout
def show(sha, *args):
return git("show", "--no-color", "--no-ext-diff",
"--format=", *args, sha)
def stat_plus_full(sha):
parts = [show(sha, "--stat")]
names = show(sha, "--name-only", "-z", "--diff-filter=d")
for path in names.split("\0"):
path = path.strip("\n")
if not path:
continue
try:
body = git("show", f"{sha}:{path}")
except subprocess.CalledProcessError:
continue # submodule or other non-file entry
if "\0" not in body: # skip binary files
parts.append(f"=== {path}\n{body}")
return "\n".join(parts)
def make_counter():
if os.environ.get("ANTHROPIC_API_KEY"):
import anthropic
client = anthropic.Anthropic()
model = os.environ.get("MODEL", "claude-opus-5-5")
def count(text):
if not text.strip():
return 0
r = client.messages.count_tokens(
model=model,
messages=[{"role": "user", "content": text}],
)
return r.input_tokens
return count, model
import tiktoken
enc = tiktoken.get_encoding("o200k_base")
def count(text):
return len(enc.encode(text, disallowed_special=()))
return count, "tiktoken o200k_base"
def main():
n = int(sys.argv[1]) if len(sys.argv) > 1 else 5
count, name = make_counter()
shas = git("log", "--no-merges", f"-n{n}",
"--format=%h").split()
cols = [*FORMATS, "stat+full"]
print(f"tokens counted with: {name}")
print(f"{'commit':<12}" + "".join(f"{c:>10}" for c in cols))
for sha in shas:
row = [count(show(sha, *a)) for a in FORMATS.values()]
row.append(count(stat_plus_full(sha)))
print(f"{sha:<12}" + "".join(f"{v:>10}" for v in row))
if __name__ == "__main__":
main()
Install the two packages with pip install anthropic tiktoken, then run python3 diff_tokens.py 5 in a clone of a public repo you know well, or in your own. --diff-filter=d (lowercase) excludes deleted files, since a deleted file has no post-change version to print. -z keeps unusual file names from being quoted. Thirty-five count calls per run at the default of five commits sits far under the lowest tier's limit. With a key set, the script sends every rendered diff and every changed file in full to Anthropic's API. On code you're not allowed to share, leave the key unset and count offline with tiktoken.
To measure what your agent actually reviews, swap the show(...) calls for a branch diff such as git diff -U0 main...HEAD. The idea doesn't change.
Reading your table
There is no results table in this post on purpose. The numbers depend on your languages, how long your functions are and how big your commits are, so the useful table is the one your repo prints. A few things to read for:
- Compare columns, not absolutes. Each API count includes the small cost of wrapping text in a user message, the same in every column. Anthropic also calls the count an estimate that can differ slightly from what a real request uses.
-
Count with the model you run. The same page says Claude 4.7 and later models use a newer tokenizer that produces "approximately 30 percent more tokens" for the same text than earlier models. A tiktoken count is OpenAI's encoding, a proxy for shape only. Set
MODELto the model your reviewer uses. -
The gap from
-U0to-U10grows with the number of hunks. Each hunk adds up to twenty lines at-U10, fewer when hunks sit close together and git fuses them. -
Wtracks function length. A one-line fix in a 300-line function prints roughly 300 lines. Look for the rows whereWjumps far pastU10. Those are the commits where git's idea of "the function" was bigger than yours. -
stat+fullgrows with file size. A typo fix in a 2,000-line file costs the whole file.
Remember that your reviewer's own prompt, tools and instructions sit on top of all of this. The table measures only the diff.
What -U0 hides and what -W costs
Take a small function, with an agent's change on line 9:
export function refund(order, amount) {
if (order.status !== "paid") {
throw new Error("order is not paid");
}
if (amount <= 0) {
throw new Error("amount must be positive");
}
const cents = Math.round(amount * 100);
return payments.refund(order.id, cents, { force: true });
}
Say the old line 9 had no { force: true }. Whether that flag is safe depends on the status check on line 2.
- At
-U0the reviewer gets line 9, before and after, and a hunk header namingexport function refund(order, amount) {. It knows where it is. It cannot see a single guard. - At
-U3it gets lines 6 to 8 above and the closing brace below: the tail of the amount check and thecentsline. The status check is still out of frame. - At
-U10or-Wit gets the whole function, guard included.
A reviewer with file tools can open refund.js and read the rest, at the price of an extra tool call and the whole file's tokens. A one-shot reviewer behind a single API call can't. It reviews what you sent.
-W has its own cost. It finds a function's start with the same hunk-header rule, and the commit that added it, by René Scharfe, says plainly that "there is no way to explicitly find the end of a function", so the context runs on until the next recognised function line. Git has built-in drivers for Python, Go, PHP, Java, Rust, C# and others, switched on per path in .gitattributes:
*.py diff=python
*.go diff=golang
*.php diff=php
The list has no JavaScript or TypeScript entry, so .ts files fall back to the default rule. By that rule, an indented method inside a class doesn't count as a function line, because it starts with a space. Change one line in a method and the nearest line -W can anchor to is export class OrderService {, which means you should expect the whole class from there down to the next unindented line that starts with a letter. If your TypeScript W column looks huge, that is the likely reason. You can define your own pattern with diff.<driver>.xfuncname, as the gitattributes page describes.
More context also isn't free in quality. Chroma's Context Rot report tested 18 models and found that "model performance consistently degrades with increasing input length." And code context isn't the cheapest win. The ContextCRBench benchmark found that "textual context yields greater performance gains than code context alone." The PR description and the issue may be worth more than ten extra lines of code.
The human side: by turn or by file
You face the same choice when you review agent work yourself. One big diff of the whole branch is the human version of stat+full: everything at once, and you have to rebuild the story. Smaller units help a person the way a tight hunk helps a model.
I build Hermes IDE, so weigh this with that in mind. Hermes saves every agent turn, so you can diff or restore any single turn, with undo. The Review Desk (⌘G, or Ctrl+Shift+G on Windows and Linux) shows the changes by turn or by file, with risk flags, and a line comment goes back to the agent that wrote that line. Reviewing by turn means you read one prompt's worth of change at a time, the same move as sending a review agent one turn's diff instead of the whole branch.

The Review Desk (⌘G) in By turn view for a Claude Code task in orders-api: two turns listed (T1: 3 files +69 −1, T2: 1 file +1 −0), turn 1's diff of src/db.ts adding a refunded status, the changed-files list, and a commit message drafted from the turns.
Hermes runs each coding agent in its own terminal, on its own branch and worktree. It is free, source-available under BSL 1.1, and runs on macOS, Windows and Linux. It is not affiliated with Nous Research, Anthropic, OpenAI or any agent vendor. Download it or read the source on GitHub.
Picking a default
Run the script on five commits from your main repo, then decide with your own numbers.
Start with git config diff.context 5. It changes what git diff prints when no -U is given, and that includes the times an agent runs a bare git diff itself. Then add diff=python, diff=golang or your language's driver to .gitattributes, so -W and every hunk header find real function boundaries.
Git has no flag for LAURA's rule, the enclosing function capped at three times the diff length. Your review script can approximate it: render -W, and if it comes out more than three times the size of -U0, send -U10 instead.
And send the PR description and the linked issue with the diff, since ContextCRBench found text context helps more than code context alone.
If this was useful
The script counts tokens before a request goes out. Once your review agent runs on every PR, you also want to know afterwards what it read, what it said and what each run cost. The LLM Observability Pocket Guide helps you pick the tracing and evals tools that record that, so a jump in your W column shows up as a line on a dashboard instead of a surprise on the invoice. If you review agent work by hand too, Hermes's Review Desk is free to try.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.
