I Stopped Eyeballing Agent Diffs, and the 2.8.0 View Limits Are Why
Version 2.8.0 caps how much of a file and how much of a large history diff you can view. Losing the ability to scroll to the end forced a better split: let a script decide what machines can catch, and spend human attention only on what they cannot. Includes a working script and the defects it found in a real repository.
The scroll stopped partway through the diff. Version 2.8.0, released on August 12, added caps on how much of a file the viewer will render and how much of a large history diff it will show. The caps shipped alongside a fix for crashes in conversations containing large command output, so on the stability side they are welcome.
The problem was that I had been working under an assumption: that I read everything an agent touched. Hitting the cap made me notice that the assumption had never actually been tested. Back when I could see all of it, was I really seeing all of it?
This is the review routine I built to answer that question. In November I re-ran the August version against the same planted defects, and the script itself turned out to have three holes. The fixes are included below, along with the exact version I use now.
What I Was Missing While I Could Still See Everything
The answer took one command. As an indie developer I run a technical blog whose Japanese-language repository held 1,006 MDX articles at the time (August). I counted how many stray backslash escapes were sitting inside published code blocks.
Twenty-eight files, 130 lines. They looked like this:
// what readers were actually servedif (\!researchOutput.sources || researchOutput.sources.length === 0) {
\! is not valid JavaScript. Anyone copying that line gets a syntax error immediately. The backslash came from shell history-expansion escaping when the file was written through a shell, and it simply stayed there.
None of those lines were ever hidden. I looked at a diff before every commit, which means I scrolled past that screen 130 separate times. The view cap did not hide anything from me. It made visible the part I had only believed I was reading.
Reading Effort Does Not Scale With Diff Size
Once I looked at the structure, the failure stopped being surprising.
Adding one article means two files, Japanese and English. Articles in this repository run to a median of 211 lines, with the largest at 1,265. A single addition produces a diff somewhere between 400 and 2,000 lines. Mix in edits to existing articles and it grows from there.
The number of lines a person can read attentively does not grow with the diff. What grows is the speed at which you feel like you have read them. A two-character anomaly like \! is exactly the kind of thing that disappears at that speed.
Defect
Machine detection
Human detection
Stray escapes, broken syntax
Reliable
Degrades with diff size
Bare URLs, broken markup
Reliable (if the check is written correctly; see below)
Same
Links to pages that do not exist
Reliable
Effectively impossible
Prose that contradicts the code below it
No
Yes
Whether the claim is actually true
No
Yes
I had been assigning myself the top half of that table. That was the real mistake, and the cap was the nudge to fix the split.
Let the machine count; keep the doubting for myself. Once I settled on that division, review started to feel different in my hands.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You will be able to clear the parts of an agent's change that must not slip through, without reading the whole diff
✦You will be able to separate the defects a machine can catch from the ones only you can catch, before broken code reaches your readers
✦You will be able to add a review step whose cost stays flat no matter how large the change is
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
The new rule is simple. Stop handing a raw diff to a human. Produce two things instead:
A digest with a fixed length. It carries what a machine already decided, plus pointers to where a human should start.
The full patch, written to a file. Never discarded, but never opened by default.
The fixed length is what matters. Whether the change is 400 lines or 4,000, the first thing you read is the same size. Review time stops tracking diff size.
Here is the current version (v2), after the November re-check. It differs from the August script in the three places marked 1) to 3) in the comments.
#!/usr/bin/env bash# review-digest.sh v2 — split a diff into a one-screen digest and a full artifact# usage: ./review-digest.sh uncommitted work (new files included)# ./review-digest.sh origin/main against a revision (new files included)# written for macOS's stock bash 3.2: no arrays, no mapfileset -uo pipefailBASE="${1:-HEAD}"OUT_DIR="${REVIEW_OUT_DIR:-.review}"mkdir -p "$OUT_DIR"FULL="$OUT_DIR/diff-full.patch"NEW_LIST="$OUT_DIR/untracked.lst"# 1) temporarily mark untracked files intent-to-add so they appear in the diffgit ls-files --others --exclude-standard -z -- . ":(exclude)$OUT_DIR" > "$NEW_LIST"if [ -s "$NEW_LIST" ]; then xargs -0 git add -N -- < "$NEW_LIST" # 2) always undo it on exit, so stash and commit -a are not affected trap 'xargs -0 git reset -q -- < "$NEW_LIST"' EXITfigit diff "$BASE" > "$FULL"; STATUS=$?if [ "$STATUS" -ne 0 ]; then echo "git diff failed (exit=$STATUS)" >&2 exit "$STATUS"fiSTAT=$(git diff --stat "$BASE" | tail -1)NUMSTAT=$(git diff --numstat "$BASE")# 3) keep added lines only, and strip the leading + before checkingADDED=$(grep -E '^\+' "$FULL" | grep -vE '^\+\+\+ (b/|/dev/null)' | sed 's/^+//' || true)E_BANG=$(printf '%s\n' "$ADDED" | grep -c '\\!' || true)BARE=$(printf '%s\n' "$ADDED" | grep -E '(^|[[:space:]])https?://' \ | grep -vE '\]\(|<https?://|"https?://' | grep -c . || true)MDTBL=$(printf '%s\n' "$ADDED" | grep -cE '^[[:space:]]*\|[-: |]+\|[[:space:]]*$' || true)NEWCOUNT=$(tr -cd '\0' < "$NEW_LIST" | wc -c | tr -d ' ')echo "== change size =="echo " ${STAT:- (no changes)} (new files: ${NEWCOUNT})"echoecho "== machine findings (fix these before opening the full patch) =="echo " stray \\! escapes : ${E_BANG} lines"echo " bare URLs : ${BARE} lines"echo " markdown table rules : ${MDTBL} lines"echoecho "== needs human eyes (largest changes first) =="printf '%s\n' "$NUMSTAT" | sort -rn | head -5 | while read -r add del path; do [ -z "${path:-}" ] && continue printf ' %5s+ %5s- %s\n' "$add" "$del" "$path"doneechoecho "full patch: $FULL ($(wc -l < "$FULL" | tr -d ' ') lines)"# exit non-zero when anything was found[ "${E_BANG:-0}" -eq 0 ] && [ "${BARE:-0}" -eq 0 ] && [ "${MDTBL:-0}" -eq 0 ]
Dropping in a new file with one line of each defect produces this (November 2026, Ubuntu 22.04, git 2.34.1, bash 5.1):
== change size == 1 file changed, 3 insertions(+) (new files: 1)== machine findings (fix these before opening the full patch) == stray \! escapes : 1 lines bare URLs : 1 lines markdown table rules : 1 lines== needs human eyes (largest changes first) == 3+ 0- _probe.mdfull patch: .review/diff-full.patch (9 lines)
Exit code 1, and afterwards git status --short still showed ?? _probe.md — the repository was back exactly where it started. Twelve lines to understand the situation and know what to fix.
The First Version Found Nothing at All
When I first ran the August prototype, all three counters read zero. This was immediately after I had planted a file containing all three defects.
The cause is git diff HEAD: untracked files do not appear in a diff. When an agent creates a new file, its entire contents fall outside the check. Newly created files are the ones you most want inspected, and by default they pass through untouched.
The fix is intent-to-add (git add -N). Registering only the file's existence in the index makes its body show up as added lines.
Command
Untracked files
git diff
Not shown
git diff HEAD
Not shown
git diff origin/main
Not shown
git add -N then git diff HEAD
Shown as added lines
git commit -a with intent-to-add left in place
The file is committed
git stash with intent-to-add left in place
Fails on my git 2.34.1
I did not know about the last two rows in August. More on them in the next section.
A gate you believe is running while it inspects nothing is a real failure mode. Whenever you add one, feed it an input that must fail and confirm that it does. The same canary idea — proving a mechanism actually fires rather than merely loads — is how I approached hooks that load but never run.
Three More Holes I Found in November
I re-ran the August script against the same defect-laden file in November. It did not go well, and every cause was in how I had written it.
Hole 1: bare URLs at the start of a line counted as zero
The August version checked added lines with their leading + still attached. The bare-URL rule matches https:// at the start of a line or after whitespace, so a line like +https://example.com starts with + and never matches.
URLs pasted mid-paragraph were caught; a URL alone on its own line — the most common way people paste one — was not. The sample output I published in August showed one bare URL. Re-running the same input in November showed zero. I believe I skipped the re-check when copying the output into the article.
v2 strips the leading + with sed 's/^+//' before any check, and the table-rule regex no longer assumes the + is there.
Hole 2: passing a revision made new files vanish
The August version only ran git add -N in the no-argument branch. Call it as ./review-digest.sh origin/main and untracked files never reached the diff; the script reported nothing and exited 0.
Invocation
August version
v2
No argument, new file with 3 defects
exit 1 (bare URLs shown as 0)
exit 1 (all three counted)
HEAD given, same new file
exit 0, "no changes"
exit 1 (all three counted)
git status afterwards
A _probe.md (intent-to-add left behind)
?? _probe.md (unchanged)
Having an agent work on a branch and comparing against origin/main is the use I actually cared about most. That was exactly the call that slipped through.
Hole 3: intent-to-add was never undone
In August I wrote that intent-to-add "does not stage anything, so it leaves your later git add behavior alone." That was only half true.
First, git commit -a. An intent-to-add file counts as tracked, so commit -a picks it up. The official git add documentation for -N even lists committing such files with git commit -a as one of its uses. A throwaway probe file can end up in history without you noticing.
Second, git stash. On my git 2.34.1, a leftover intent-to-add entry made it fail like this:
error: Entry 'new2.md' not uptodate. Cannot merge.Cannot save the current worktree state
This combination has been reported repeatedly on the git mailing list, and behavior may differ by version, so it is worth checking once on whatever git you have.
v2 writes the untracked list to untracked.lst before starting and uses trap ... EXIT to git reset -q -- those paths on the way out. Whether the script crashes midway or exits non-zero because it found something, the index ends up where it was.
Piping the Digest to head Throws the Verdict Away
One more thing surfaced after a few days of use. The digest grew a little, so I trimmed it at the call site:
./review-digest.sh | head -20 # the verdict is now gone
By default a pipeline exits with the status of its last command. head succeeds, so even when the script returns 1 the caller receives 0. Findings are detected and nothing stops.
The tempting fix is set -o pipefail at the call site. When I tried it, a different problem appeared: with something like | head -1 that stops reading early, the script dies of SIGPIPE and you get 141 even when there are zero findings. Now it stops when it should not. I measured when that false red shows up in the pipefail check that turned red once output grew.
Keeping the digest short is the script's job, so the call site should just run it. When trimming is unavoidable, resolve the verdict first:
if ./review-digest.sh > .review/digest.txt; then echo "no machine findings"else head -20 .review/digest.txt # trim only after the verdict is captured exit 1fi
Call
With defects
Without defects
./review-digest.sh
1
0
./review-digest.sh | head -20
0 (verdict lost)
0
set -o pipefail, then | head -1
141
141 (false red)
Write to a file, then head
1
0
Truncation drops more than it appears to.
Troubleshooting by Symptom
These are the symptoms I actually ran into after adopting the routine, with how to confirm each and how to fix it. Working down the list from the top tends to find the cause quickly.
Symptom
Likely cause
How to confirm
Fix
Planted defects, all three counters read 0
The new file is still untracked
git status --short shows ??
Mark intent-to-add before diffing, as in v2 step 1)
Passes only when a revision is given
intent-to-add lives in the no-argument branch only
./review-digest.sh HEAD; echo $? prints 0
Pick up untracked files before branching
URL-only lines are not counted
The leading + was not stripped
grep '^+https' .review/diff-full.patch matches
Pipe through sed 's/^+//' before checking
git stash fails after a run
An intent-to-add entry remains in the index
git status --short lists a new file as A
git reset -q -- <file>; v2 does this via trap
A throwaway file got committed
git commit -a with intent-to-add in place
git show --stat HEAD
git reset --soft HEAD~1, then switch to v2
CI is green despite findings
The call site pipes to head
Remove the pipe and echo $?
Write to a file, then display
Red with 141 and no findings
pipefail plus a reader that stops early
echo "${PIPESTATUS[*]}"
Same as above; avoid the pipe
The full patch grows on every run
.review/ is not ignored
git check-ignore .review/diff-full.patch
Add .review/ to .gitignore
Where to Put the Check, and How I Choose
The idea — let a machine clear the mechanical defects first — stays the same, but there are several places to run it. Here are the ones I tried or considered.
Where
Strength
Weakness
Good fit
Eyeballing the editor's diff view
No setup
Large diffs hit the cap since 2.8.0; two-character anomalies vanish
Small edits of a few dozen lines
git diff in a terminal pager
No cap, the whole patch is readable
Reading volume is unchanged and attention runs out
Investigating a bug with an unclear cause
Running the digest script by hand
Fixed-length output, light to adopt
Useless on the days you forget
Taking an agent's work in batches
A git pre-commit hook
Never forgotten
Hooks do not travel with a clone; --no-verify skips them
A repository only you work in
The first CI job
Runs no matter who pushes
You wait for the result
The last gate before publishing
In my situation I run the digest by hand at each checkpoint and also put the same script in the first CI job — locally to notice early, in CI as a gate nobody can skip. Whenever I relied on only one of them, there was always a day I forgot the local run.
Three Common Misreadings
"The view cap means part of the diff is lost"
The cap applies to what the viewer displays, not to the diff data. Write git diff to a file and the whole thing is still there. What 2.8.0 changed is the default for how much is shown.
"git diff HEAD includes new files"
It does not. With or without HEAD, and with origin/main as the revision too, untracked files never appear. If you want an agent's new files inspected, you have to tell the index they exist, for example with intent-to-add.
"Every \! is a bug"
In a shell, find . \! -name "*.bak" is perfectly valid, and v2 counted that line as a finding when I tried it. The digest's number is a list of things to look at, not a list of things to fix. When it is non-zero, pull the lines with grep and judge them per language. In my article repository, I treat \! inside JavaScript and TypeScript code blocks as a defect.
Deliberately Narrow What a Human Looks At
Once the digest passes, I open only the top two or three files by change size, and I look strictly for things a machine cannot judge:
Does the prose say the same thing as the code beneath it?
Is the reason for the chosen approach written down anywhere?
Was the described behavior actually verified?
The dangerous code an agent writes is rarely the code that fails to parse. It is the code that parses cleanly while drifting slightly from its own description. Machines catch the first category reliably; only people catch the second. Since splitting the roles, I notice the second kind more often. Reading everything meant spending my attention before I ever got there.
The 2.8.0 caps apply to the viewer and to large history diffs. They do not remove the times you genuinely need the whole thing. My split looks like this:
Situation
What I open
Routine review
Digest only; top files if warranted
Digest exited non-zero
Just the offending lines, via grep
A bug whose cause is unclear
The saved .review/diff-full.patch, in an editor or pager
The full patch is on disk, so it is there when I want it. The cap is on the viewer, not on the data. Once that distinction was clear, the change stopped feeling like a restriction and started feeling like a different default.
Three Steps to Put This in Place
Setup takes about fifteen minutes. This is the order I used.
1. Drop the script in and ignore its output directory
Put review-digest.sh at the repository root or under scripts/, and make it executable. Add .review/ to .gitignore. v2 already excludes .review/ from the untracked list, but without the ignore rule git status gets noisy on every run.
2. Feed it something that must fail, and confirm it cleans up after itself
Skipping this is how you end up with the detector that detects nothing. Write a throwaway file holding one line of each defect and run the script both with no argument and with a revision. Both should exit 1, and git status should not show an A afterwards.
printf 'if (\\!ok) {}\nhttps://example.com\n| --- | --- |\n' > _probe.md./review-digest.sh > /dev/null; echo "no-arg exit=$?" # 1 means it is working./review-digest.sh HEAD > /dev/null; echo "HEAD exit=$?" # 1 means it is workinggit status --short _probe.md # still ?? means clean-up workedrm _probe.md
3. Put it ahead of your existing checks
In a production pipeline, place it before the expensive steps — tests, builds, linters. There is no reason to spend a full build on a two-character anomaly.
./review-digest.sh > .review/digest.txt || { cat .review/digest.txt; echo "machine findings; fixing first"; exit 1; }npm run lint && npm test
Nothing existing has to be replaced; the digest only goes in front. Runtime stays essentially flat regardless of diff size: with a 3,000-line new file in the diff, it finished in under 0.1 seconds on my machine.
What to Do Next
If you use a similar digest script, check once — using the call that passes a revision — whether a new file full of defects really makes it fail. Mine let that call through for three months.
I still have only three checks in the digest and I am adding them one at a time. If you have found a check worth including, I would like to hear about it.
Share
Thank You for Reading
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.