When iterating on a PR with a reviewer, **address every in-scope flagged item**,
regardless of severity label. The reviewer's "Not a blocker", "minor", "nit",
"optional", "consider", or "if you want" labels are for prioritization, not a
free pass for the implementer.

For each flagged item, do exactly one of:

1. **Fix it in this PR.** The default path --- most nits are 1--3 line changes.
2. **Defer.** Only when the fix expands the PR's scope (new feature, broader
   refactor, separate concern), the requester has explicitly said this PR
   shouldn't grow, or the flagged content isn't actually yours to fix here
   (see the `main`-sync case below). File a follow-up issue and reference it
   in a PR comment so the item isn't lost --- except in the `main`-sync case,
   where the "follow-up" is fixing it on `main` directly, not a new issue.

Then trigger another review and repeat until the PR is **fully clean** --- zero
flagged items under any heading, no "non-blocking", "harmless", "minor
observation", or "could improve" sections. "Looks good" / "no findings" /
"approved" with no follow-on bullets is the bar. Resolve every inline review
thread along the way, leaving only the final all-clear exchange.

**Always resolve an inline thread the moment its comment is successfully
addressed** --- the fix pushed and a reply posted naming it --- in the same
pass, whatever workflow you're in: a formal `ard`/`ardi` round, a CI-monitor
nudge, or a one-off fix outside any loop. Addressing without resolving leaves
a thread that reads as outstanding work to every later reviewer, blocks
[`fully-clean`](fully-clean.md)'s every-inline-thread-resolved criterion, and
drags stale noise into the next review round. The per-disposition settlement
rules in `ard` step 4b still govern the exceptions: a **Rebut** stays open
until the reviewer drops it, and an **Address** you're not confident fully
settles the concern gets a reply asking for confirmation instead of a
resolve. The `resolve-pr-threads` skill sweeps any stragglers, but it's a
backstop --- resolve-on-address is the default, not a cleanup step.

Do **not** report "ready to merge with one minor nit noted" / "harmless as-is" /
"can address if you want" --- that hedging just pushes triage back to the
requester.

**A round count is never a reason to stop, and "the reviewer keeps finding
things" is not a finding about the reviewer.** There is no threshold after
which unaddressed items become acceptable: keep requesting reviews and keep
dispositioning findings until a review comes back with none. The only exits
are a totally clean review, a genuine per-item deadlock, or the user calling
it. Reasoning of the form "we have done N rounds, shall we accept the current
state?" is the same hedging this paragraph bans, moved up from one finding to
the whole loop --- see
[`ardi`](../../skills/ardi/SKILL.md)'s "Stopping conditions" for why it fails
and for the case record.

**Noise is per-item, not per-round --- don't stop the whole loop over one
recurring flag.** A long-running PR can have both real findings (worth fixing
every round) and one specific item the reviewer re-raises verbatim round after
round even though it's already deferred/tracked (e.g. a file-length guideline
already split into a follow-up issue). Keep fixing every *new* finding as it
appears --- don't let the recurring item make you stop processing genuinely
new ones. But stop re-litigating *that one item* every round: reply once
pointing at the tracked issue, and hold on it specifically rather than
re-deferring it on each pass. Surface the pattern to the user (which item, how
many rounds, where it's tracked) and let them decide whether to resolve it now
(e.g. do the split) or leave it as accepted recurring noise --- don't decide
unilaterally to either keep re-processing it or silently drop it. (rme#706 ran
100+ review rounds: each round's *new* findings --- a missing derivation step,
a missing i.i.d. hypothesis, an unverified citation locator --- got fixed
every time; the one recurring file-length flag got a single reply-and-hold
each round until the user weighed in.)

**When a finding is a pattern (a formatting/style rule broken in one spot),
apply it everywhere it recurs in the same file, not just the flagged line.**
A reviewer that flags one inconsistent list-item format is telling you about
the rule, not just that one item --- fix every occurrence in the same file that
breaks it in the same pass, rather than waiting for the reviewer to flag each
occurrence in a separate round. Re-scan the whole changed file for the same
pattern before pushing the fix.

**That rule's scope is "the same file", and a reviewer who enumerates the sites
is the reason the scope goes unquestioned.**
A pattern finding usually arrives with a list attached: three spots, named,
each with a file and a line.
That list is a snapshot of where the reviewer happened to look, never the
extent of the pattern.
A reviewer reports the instances it noticed while reading a diff, which is not
the same operation as sweeping for them.

Inheriting the list rather than deriving it reads as responsive rather than as
under-scoped, which is what lets it survive a round.
The reviewer found the problem, so its account of the problem carries the
authority of the finding itself, and fixing exactly what was named is
indistinguishable --- from the inside and from the thread --- from fixing the
pattern.
The failure then reproduces one round later in a file the enumeration did not
name, which is the round the rule above exists to save.

The tell is lexical, and it sits in your own reply: **"fixed all three spots
you named"**.
Quoting the reviewer's count is the signal that the set was inherited, since a
derived set has no reason to agree with a number someone else supplied.

So derive the site list with a command: pipe the whole diff through a grep for
the flagged phrase, and widen the search past the diff when that phrase was
copied in from somewhere else.

Pick the pattern shorter than the phrase, though.
This corpus mandates semantic line breaks, so a multi-word phrase routinely
straddles a newline, and a line-oriented grep for the whole thing then matches
nothing --- a derived set can come back empty for a formatting reason rather
than a factual one, which is the same false-negative this file treats at length
under its own semantic-line-break corollary.
A short distinctive fragment is the reliable choice.

Then report what was **swept** rather than what was fixed.
"Grepped the whole diff for `X|Y|Z`, four hits, all four fixed" is checkable,
while "fixed all three" asserts a scope nobody measured.

This is [`derive-dont-enumerate`](derive-dont-enumerate.md)'s principle applied
to a review finding's site list rather than to work items, and that is the
transferable part.
Every fix is individually correct while the coverage claim over them is false,
so the gap is a property of the **set** rather than of any member --- nothing
in the diff, the tests, or the thread reports it.

Distinct from
[`algorithmatize-checks`](algorithmatize-checks.md)'s "never predict which case
will fail; enumerate the class", which shares this remedy and has a different
trigger.
There the list is one **you** produced from intuition, so the rule fires on
your own naming of a member.
Here the list arrived from someone with more standing to write it than you had,
which is why nothing about accepting it feels like guessing.

- **Do:** derive the site list by grepping the whole diff for the flagged
  phrase, and fix what the grep returns.
- **Do:** report the sweep --- the pattern searched and the hit count ---
  rather than the number of sites you fixed.
- **Don't:** treat a reviewer's enumeration as the extent of the pattern; it is
  the extent of that reviewer's read.
- **Don't:** write "all N spots you named" into a reply, since quoting the
  reviewer's count is the tell that no sweep ran.
- **Don't:** read a null result as "no further sites"; it means no further hit
  for that pattern, and a differently-worded instance would not have matched.

(`Morrison-Lab/gha#398`, 2026-08-03: round 1 flagged an unquantified
superlative, "the corpus's most common paragraph opener", and named three sites
--- `CLAUDE.md`, a `changelog.d/` fragment, and a code comment in
`check-new-line-breaks/check-new-line-breaks.py`.
Commit `698d0af` touched exactly those three files, and the reply read "in all
three spots you named".
Round 4 then found a fourth site, a comment block in
`check-new-line-breaks/tests/test_check_new_line_breaks.py`.
Its finding opened "Fourth site with the same unquantified-superlative issue,
missed by the two rounds that fixed the other three" --- the reviewer's words,
not the code comment's, which says nothing about review rounds.
The sweep was available in round 1 and would have closed it there:
`git diff origin/main...e0e08e2 | grep -E 'most common|single most|house style'`,
run against round 1's own head, returns five hits across all four files.
The same grep at the fixed head returns no further hit for that pattern, which
is the other half of the check --- though only for that pattern, since a
superlative worded differently would not match it.)

**When a prose fix changes wording that's also paraphrased elsewhere in the
same PR (a CHANGELOG entry, a PR description, a cross-reference), sync that
copy too.** A CHANGELOG entry written before the review lands often quotes or
paraphrases the exact phrase a reviewer later flags; fixing the source
prose but leaving the paraphrase stale reintroduces the same wording issue
one file over. Grep the diff for the flagged phrase before considering the
finding closed. (ai-config#373: fixed "routing/dispatch site" in the skill
per review, but the CHANGELOG entry still said it until a follow-up commit.)

**When syncing copies, search the diff for the claim, not the files or symptom
already in front of you.**
The paragraph above says to grep the diff, and both words matter.
A path-scoped grep over the files already open is not a diff search, however
real the hits it returns are.
The scope is the silent variable: the command succeeded, but it searched the
author's working memory rather than the change.
Pipe the diff itself, so the search space is the whole PR diff:

```bash
git diff origin/main...HEAD | grep -n "<figure-or-phrase>"
```

Run that after committing, not before.
Like `check-new-line-breaks`, any `origin/main...HEAD` scan reports on `HEAD`,
so a pre-commit run describes the old committed text and can make a fixed
working tree look unfixed.

The same failure can hide in the search term instead of the path.
When a review retires a rationale, search for statements of the retired
criterion, not only for the word or contradiction that exposed it.
The exposing detail usually appears once; the criterion is what got copied
around.

- **Do:** run whole-diff searches for synchronized figures and phrases, after
  committing the fix, and report the before/after counts.
- **Do:** when a rationale is retired, search for every wording that states that
  rationale or criterion, not only for the symptom word that made it fail.
- **Don't:** substitute `grep -rn <term> <files-you-had-open>` for grepping the
  diff.
- **Don't:** accept a search for the visible contradiction as proof that the
  retired claim itself is gone.

(Morrison-Lab/ai-config#981, round 2 commit `f616dc5a`, did both.
A count fix followed this section's rule in name, but ran
`grep -rn "122" hooks/*.py`, scoped to the two files already open.
That missed `hooks/hooks.json:56` and
`shared/workflow/incidents-dont-repeal-decisions.md:94`, so round 3 found the
PR still disagreeing with itself: 121 in two places and 122 in two others for
one unrecountable measurement.
The round 3 fix `05486216` used the diff as the scope; after committing, the
counts were `122: 0` and `121: 5`.
The same round retired a rationale that `Explore` and `Plan` were exempt
because they lacked `Edit`, `Write`, and `NotebookEdit`, while `Bash` was the
hole in that role contract.
Searching only for `Bash` missed a nearby code comment and two test labels that
still said the agents were read-only "by definition".
Those were corrected to the harness's declared read-only role, and both review
threads were resolved.)

**The PR description is on that list and is the one copy grepping the diff
cannot find, so check it separately.**
A PR body is not a file, so it appears in no diff and no reviewer reads it as
part of the change under review.
That makes it the copy most likely to survive a fix, and the copy most
likely to be *read* by someone deciding whether to merge --- so a stale one
teaches the reader exactly the thing the diff was corrected to remove.

The tell is a fix to something the PR body summarizes: a behaviour change, a
mechanism, a rationale.
Re-read the description against the corrected diff before declaring the round
done, and say in the update that it was corrected, so a reader who saw the
original knows it was revised rather than always having said this.
Where the correction has history worth keeping --- a claim that was wrong and
is now right --- state it as history in the body rather than silently
overwriting, since the wrong version is what earlier comments respond to.

- **Do:** re-read the PR description after any Address that changes what the
  PR does or why, alongside the changelog check above.
- **Don't:** treat a clean `grep` over the diff as evidence every paraphrase
  is synced --- the description was never in it.

(ai-config#829, 2026-07-29: a review nit led to correcting a gha#350
attribution in `memories/github-actions.md`.
Both reviewers then approved, and the PR body still carried the original
wrong claim verbatim --- "`continue-on-error` there, the dropped implicit
`success()` here" --- because it had been written before the correction and
was not part of the diff either reviewer read.
Caught only while assembling the ready-for-merge summary.)

**Following that "state it as history" advice is what produces the next
block, because an automated reviewer reads the body as a flat statement of
intent.**
The paragraph above is right that a correction with history worth keeping
should be recorded rather than silently overwritten, since earlier comments
respond to the old version.
It has a failure mode it does not warn about, and the failure lands precisely
on the authors who follow it.

A past-tense paragraph saying a thing *was* excluded is, to a bot, not
distinguishable from a claim that it *is* excluded.
Tense is doing all the work, and nothing in the reviewer's reading of the
document preserves it.
So the more faithfully the reversal is recorded, the more confidently the
reviewer reports the diff as contradicting its own description --- and the
remedy it proposes is to revert the change, which means undoing whatever the
reversal was.

Distinguish this from an ordinary stale snapshot before answering.
A reviewer that started before your edit never saw the correction and needs
only a pointer to it.
The timestamp check further down is written about a missed *rebuttal*, but
the same `started_at` comparison decides a missed *body edit*: a body
corrected after the run began is invisible to it for exactly the same
reason, since the whole PR is snapshotted once at run start.
This one re-raises *at the corrected text*, so the timestamps clear and the
finding still stands.
Compare the run's start time against the edit, then read which passage the
new verdict quotes --- if it is quoting your history section, this is the
case, not that one.

- **Do:** state the current content first, marked as current, before any
  history.
- **Do:** put the reversal in its own section that opens by saying it is
  history.
- **Do:** make sure the "what is excluded" section does not name the reversed
  item at all, in any tense.
- **Don't:** rely on past tense alone to carry the distinction.
- **Don't:** revert a maintainer-requested change because a reviewer read the
  history as current --- rebut, and escalate rather than comply.

Be honest about the residual: all of that can be applied and a further run
can still block, at which point the only remaining move is deleting the
history outright, which costs the earlier comments their referent.
That trade belongs to the human, not to the agent driving the PR.

(Morrison-Lab/ai-config#843, 2026-07-30: the maintainer asked for a fourth
tool on an allowlist that had shipped with three.
Jules blocked twice.
The first was an ordinary stale snapshot --- its run started at `02:56:28Z`,
two seconds before the body was corrected.
The second re-ran at `03:00:31Z` against the corrected body, with a new
session id and a different cited line number, read the reversal-history
section, and praised the description for "explicitly documenting which
permissions should be intentionally excluded" while demanding the requested
tool be removed.
`claude-review` saw the same inconsistency at the same stale head and graded
it a minor, explicitly non-blocking prose note.
Escalated; the human merged past it.)

**The same sync is needed when the review fix is to CODE BEHAVIOR rather than
to wording --- and that case is easier to miss, because nothing about fixing a
bug points at the changelog.**
The rule above fires on a recognizable trigger: a reviewer quotes a phrase, so
you go looking for that phrase.
A behavior finding gives you no phrase to grep.
You change the code, update the PR body's description of what it now does, and
the `NEWS.md`/`CHANGELOG.md` entry --- written before the review, in prose that
described the *old* behavior correctly --- goes on asserting it.
Every later round then reviews a diff whose changelog contradicts its own code,
and no reviewer flags it, since each file reads plausibly on its own.
The shipped result is worse than a stale paraphrase: a user reading the release
notes is told the opposite of what the release does.
So after any Address that changes behavior, re-read the PR's changelog entry
against the new behavior --- not just the code and the PR body.
Fold it into the same pre-push self-review pass [`ardi`](ardi.md) already
requires; a changelog entry is a claim about the diff, so
[`fact-check-prose`](../writing/fact-check-prose.md) applies to it exactly as
it applies to any other prose in the PR.
(d-morrison/altdoc#78, 2026-07-27: review round 2 established that mkdocs
serves `/man/foo/`, not `/man/foo.html`; the code and the PR body were
corrected that round, while `NEWS.md` kept saying links point at `.html` under
"`mkdocs` and `quarto_website`" through two further clean review rounds.
Caught by a `main`-sync merge conflict that happened to land in that entry ---
not by any review, and not by any check.)

**Tighter still: a changelog entry can contradict its own commit message, in
the same commit, with no review in the loop at all.**
Both cases above need a review round to set them up --- a reviewer quotes a
phrase, or a finding changes behaviour --- so the trigger to go looking is
external.
Here there is none.
The commit message and the changelog entry are written minutes apart, by you,
in the same commit, and disagree.

The reason it survives is that the two are drafted in different registers.
A commit message argues for the change and reaches for the sharpest true
statement of the mechanism; a changelog entry describes the change for a
release note and reaches for the tidiest one.
Nobody reads them side by side afterwards.
A diff review sees one, a `git log` sees the other, and no check compares
them --- so the contradiction ships, and the release notes are the half a
user actually reads.

The check is mechanical and belongs in the pre-push self-review pass
[`ardi`](ardi.md) already requires: after writing a rationale into a commit
message, grep that same commit's prose changes for a claim about the same
mechanism, and read the two together before pushing.
Where they differ, the commit message is usually the correct one, because it
was written while the mechanism was in front of you.

(`ucdavis/bcs#463`, 2026-07-30: `a0f4113d`'s commit body said `update_trigger`
"can set `enabled: false` and rewrite a routine's prompt and cron outright".
The `NEWS.md` entry edited by that same commit justified excluding a different
tool on the grounds that the allowed set only changes *when* routines run ---
which the commit body directly refutes, and which is wrong about
`create_trigger` too, since it authors a whole new routine.
Caught by `claude-review` as an inline finding, not by any check, and the
correct framing turned out to be deferred versus immediate effect.)

**One step further back: a figure inherited from the tracking issue is both
the copy git keeps and the copy nobody verified.**
The entry above explains a mismatch by *register* --- a commit message argues
for the change while a changelog describes it, so they get drafted differently
and never read together.
Here both claims sit in the same register and describe the same fact.
Only one of them was checked.
What separates them is **provenance**: a number produced by running something,
versus a number carried over from the issue you wrote before you had anything
to run.

Two properties make it worse than an ordinary wrong number.

One of the two copies becomes permanent, and you cannot tell which from
inside the PR.
A PR body stays editable forever, while a commit message does not survive a
merge in editable form --- but which text a squash merge actually keeps is a
repository setting, and it can be either.
Configured one way the commit messages land on `main` and the PR body is
discarded; configured the other the PR body becomes the commit body and the
commit messages are dropped.

That is why the rule is *both must be right* rather than *check the important
one*.
The copy that survives is chosen by a setting most authors have never looked
at, so treating either as the draft is a coin flip.
And the odds are not even: the commit message is the one written earliest,
from the least evidence, so the configuration that keeps it is the one that
makes the weaker copy permanent.

Read a recent squash commit on `main` if you want to know which way a given
repo is set --- `git log -1 --format=%B <a squash merge>` shows it directly,
and beats reasoning about settings pages.
(Checked this way on this repo, 2026-07-30: `5670f9f`, the squash of
[#855](https://github.com/Morrison-Lab/ai-config/pull/855), carries that PR's
commit message rather than its body.
So here the weaker copy is the one that persists.)

And verifying once feels like verifying.
Running the check for the PR body produces a real sense of having established
the fact, which is what stops you checking the other place it appears.
The verification is genuine; the coverage is not.

Note the shape is the same as [`ardi`](ardi.md)'s "an instruction's own
suggested code is not exempt", one artifact over: content inherited from a
planning document does not feel authored, so the checks you apply to your own
claims do not fire on it.

- **Do:** re-run the check when a figure moves from an issue into a commit
  message, even having verified it once for the PR body.
- **Do:** read `git log -1 --format=%B` before pushing, against the same
  source the body's claims came from --- a commit message is not greppable
  from the working tree once written.
- **Don't:** copy a count, version, or path out of the tracking issue on the
  strength of having written that issue.
- **Don't:** treat "permanent in history" as settled while the PR is
  unmerged --- `git commit --amend` still works, and is usually worth a fresh
  CI round against a wrong figure reaching `main`.

(`ucdavis/bcs#465`, 2026-07-30: a submodule-pin bump whose PR body said
`CLAUDE.md` resolves 33 `@.ai-config/...` imports and whose commit message,
written minutes apart, said 25.
33 came from a script; 25 came from the tracking issue, written from
recollection.
Review named the mechanism exactly --- inherited from the issue rather than
re-checked against the file --- and graded it non-blocking on the grounds that
it was already permanent, which was the one part that was not yet true.)

**A corollary for checking any of this in a semantic-line-break corpus: a
single-line `grep` returns false negatives on your own prose.**
The instruments above and elsewhere in this file assume you can search for a
phrase you wrote.
In a corpus that mandates one clause per line, a phrase of any length
routinely spans a newline, so `grep 'flat statement of intent'` reports zero
against a file that plainly contains it.
The failure direction is the dangerous one: a missing-content check that
answers "absent" when the content is present reads as a merge having dropped
your work, which invites re-doing something already done.
Normalize whitespace before matching --- read the file, collapse `\s+` to a
single space, then search --- rather than trusting a line-oriented tool
against deliberately broken lines.
(Same day, verifying that
[#855](https://github.com/Morrison-Lab/ai-config/pull/855) had landed: two of
three greps reported present and the third reported absent, purely because
that phrase happened to straddle a line break.)

**Inline markup breaks the same search, and that variant aims the false
negative at someone else's work rather than your own.**
Whitespace is the obvious thing a line-oriented tool gets wrong, so the fix
above normalizes it.
Markup is the one nobody normalizes, because the two strings *look*
identical: a rule titled ``Run a local session in an isolated `git worktree`
by DEFAULT`` is quoted in a citation as "Run a local session in an isolated
git worktree by DEFAULT", since prose quoting a title drops its code spans.
Grep the quoted form and the definition does not match; only the citation
does.

The consequence is worse than the line-break case, and in a specific way.
There a false negative says your own merged work is missing, so you re-do
something already done.
Here it says the **cited** thing is missing, which reads as a dangling
citation --- and the prescribed response to a dangling citation is to file
an issue.
So the wrong search does not merely waste effort, it puts a false claim
about the corpus into the tracker, against a citation that resolves.
A result of exactly one hit, in the citing file, is the tell: a genuinely
dangling citation and a formatting mismatch produce the same count, and only
reading the hit distinguishes them.

Normalize backticks along with whitespace, or search a distinctive
unformatted fragment rather than the whole title.

- **Do:** account for inline markup as well as whitespace before concluding a
  quoted phrase is absent --- see the next block for which side to normalize.
- **Do:** read the single hit when a search for a citation's target returns
  only the citation itself.
- **Don't:** file a dangling-citation issue while the only evidence is a
  literal grep that found nothing but the citation --- that is the search
  failing, until a normalized one agrees.

(Morrison-Lab/ai-config, 2026-07-30: `skills/ums/SKILL.md:109` cites
`memories/preferences.md`'s worktree-by-default rule, and a literal grep for
the quoted title returned only the citation, which was reported as a
dangling reference.
The rule is at `memories/preferences.md:264`, differing from the quotation by
two backticks; a backtick-normalized search found both files.)

**Apply whatever normalization you choose to the search term as well as to
the text, or the fix produces a third false negative of its own.**
Both cases above are answered by transforming the haystack --- collapse
whitespace, strip backticks --- and that framing invites transforming only
the haystack, since the needle is the string you already know.
But a normalizer is a function, and testing `f(text)` against a raw needle
compares two different alphabets.
Strip `_` to catch `*emphasis*` and a snake_case identifier stops matching
itself: `SH_WORD_SPLIT` becomes `SH WORD SPLIT` in the file while your
pattern still carries the underscores, so a term that is present reports
absent.

This failure gets *more* likely as the normalizer gets better, which is the
part worth naming.
Every character class added to catch another markup form is another class
that occurs inside real identifiers, so the enumerate-what-to-strip approach
converges on breaking the searches it was extended to fix.
Enumerating is the wrong shape, not merely an incomplete list.

Running the same function over both sides dissolves the question, whatever
the function is:

```python
norm = lambda s: re.sub(r"[`*_\s]+", " ", s)
norm(needle) in norm(haystack)
```

- **Do:** normalize the needle with the identical function applied to the
  text, so the comparison is between two transformed strings.
- **Do:** re-test any earlier absent verdict after extending a normalizer,
  since the extension can break a term the previous version matched.
- **Don't:** enumerate which markup to strip and treat that list as the fix.
- **Don't:** test a raw search term against normalized text, however plain
  the term looks.

(Morrison-Lab/ai-config, 2026-07-30, verifying #919 on `main`: a probe
collapsing backticks, asterisks, underscores, and whitespace in the file
alone reported `SH_WORD_SPLIT` ABSENT, while `git grep -c` found it.
The same needle normalized reported present.
That was the third normalization-caused false negative of the session, and
the first produced by the remedy rather than by the raw search.)

**A flagged item that came in via a `main`-sync merge, not your own diff, is still a Defer --- just one where the follow-up is fixing it on `main` directly, not filing a per-PR issue.** This is not the ARD skill's "Acknowledge" disposition: `skills/ard/SKILL.md` reserves Acknowledge for praise or a no-ask observation, and explicitly warns against stretching it to dodge a real finding --- a redundant config line a reviewer flags is a real finding with an implied fix request, so it needs a real disposition, not a label that means "no change requested." When a reviewer flags something (a redundant config line, a stale pattern) inside a file your branch only touches because you merged `main` in to resolve a conflict, check provenance before fixing it: `git log`/`git blame` the flagged line, or just compare against `origin/main`'s current content. If it's identical to `main`, "fixing" it on your branch alone doesn't fix anything --- it just makes your branch disagree with `main` on unrelated content the next person to touch that file will have to reconcile again. Reply agreeing the finding is correct but out of scope for this PR, and leave it for whoever owns that file's actual content to fix on `main` directly --- no follow-up issue needed, since the fix target is `main` itself, not this PR's own change. (`UCD-SERG/serocalculator#503`: a review flagged `.Rbuildignore`'s `^\.posit/assistant$` as redundant with the existing `^\.posit$` pattern above it --- both lines had landed together in an already-merged `main` commit (#579), picked up via a routine `main`-sync merge, not introduced by #503's own diff. Deferred to `main` instead of fixed on the branch.)

**This generalizes to a skill's own inline restatement of a fragment it
links to.** A `SKILL.md` that links a backing `shared/` fragment for the
full detail often *also* restates the fragment's approach or word list
inline (in its `description` field, or a short procedure-step summary) so
a reader doesn't have to open the linked file. Fixing a bug in the
fragment doesn't automatically fix these inline restatements --- they're a
second, independent copy of the same claim, and a review round after the
fragment fix can catch them going stale exactly like a CHANGELOG paraphrase
does. Grep the whole PR diff for the fixed phrase/word-list, not just the
fragment file, before considering a fragment fix complete. (`ai-config#507`:
fixing `forward-references.md`'s regex left `fix-forward-references/SKILL.md`'s
own `description` field and Step 2 summary describing the old, already-fixed
approach --- caught in a second review round.)

**A bot that re-raises an item as "not addressed" may simply not have seen
your reply --- check the timestamps before treating it as an impasse.** An
automated reviewer gathers the PR's comments once, when its run starts. A
rebuttal posted after that snapshot is invisible to it, so the next round
reports the item as still open and unaddressed even though a substantive
reply is sitting in the thread. The tell is a re-raise that repeats the
original finding verbatim and speaks only to whether the *code* changed,
without engaging any argument you made. Before escalating, compare your
reply's timestamp against the review run's `started_at` (`gh run view <id>
--json startedAt`, or the `started_at` field each run carries in
`get_check_runs` when `gh` is absent): if the reply landed after the run
began, it is a stale re-raise, not a genuine disagreement.
Reply once pointing at the earlier rebuttal (link it directly --- the next
run will see it), and don't count that round toward the
rebuttal-didn't-convince-them test in `fully-clean.md`.

The ordering fix is cheap: when a round is Rebut-only, post the rebuttal
**before** anything that triggers the next review (a push, an `@claude`
mention), so it is in the snapshot the next run reads. When a round mixes
Address and Rebut, post the rebuttals first and push the code second, for
the same reason. (d-morrison/altdoc#34: a `\pkg{}` rendering rebuttal
carrying a `pandoc` run that disproved the finding's implied hazard was
posted about a minute before the follow-up review job started; that review
reported the item "wasn't addressed in `9398d5d`" and re-posted the
identical suggestion.)

**Reply-first collides with citing the fix's SHA, and the way out is to commit
between them rather than to pick one.**
The rule above is easy to agree with and still lose, because on a mixed round
the reply you want to write says "Addressed in `<sha>`" --- and that SHA does
not exist until you have committed.
So the two instructions read as mutually exclusive: reply first and you have no
SHA to cite, push first and the reply misses the next review's snapshot.
Pushing first wins that standoff by default, since it is the half that
*unblocks* the sentence you were trying to write.

The conflict is only apparent, because committing and pushing are separate
steps and only the push triggers review:

1. **Commit** the round's fixes.
   The SHA now exists and is stable.
2. **Reply** on each thread, citing that SHA.
3. **Push.** The next review's snapshot already contains the replies.

A commit that is never pushed is invisible to CI and to the reviewer, so step 2
is citing something real but not yet reachable --- which is fine for a few
seconds, and is exactly the window step 3 closes.
Note the one thing this does *not* license: the SHA you cite must come from
`git rev-parse HEAD` or `git log`, never from recollection, per the PR-body
bullet in [`ardi`](ardi.md).

- **Do:** commit, reply citing the committed SHA, then push --- in that order.
- **Don't:** treat "I need the SHA for the reply" as a reason to push before
  replying; that is the ordering the bullet above exists to prevent.

(ai-config#871, 2026-07-30: a four-finding round with three Addresses and one
Rebut was pushed first and replied to about a minute later, so the round-2
review run started before the rebuttal was visible to it.
It happened to engage the rebuttal anyway --- the evidence was in the diff as
well as the thread --- but that was luck, not the ordering working.)

**A finding can be right while its `suggestion` block is wrong --- verify
the suggested literal before applying it.**
A GitHub ```` ```suggestion ```` block is one-click-appliable, which is
exactly what makes an unverified one dangerous: the surrounding prose
argues for a change you agree with, so the concrete replacement rides in
on that agreement without being checked itself.
Treat any file path, version, flag, or command inside a suggestion as a
claim to verify, not as text to accept --- the same standard
[`fact-check-prose`](../writing/fact-check-prose.md) applies to the diff.
Accepting a bad literal is worse than ignoring the finding, because it
publishes a specific wrong value under the reviewer's apparent authority.
When the suggestion is wrong but its point stands, fix the underlying
issue your own way and say in the reply why the suggested form was set
aside --- silently deviating reads as having missed it.
(ai-config#726: a review correctly flagged that a `<path>` placeholder
didn't say where a script came from, but suggested
`<path-to-gha-checkout>/check-new-line-breaks.py` --- one directory level
too high, since the composite action's directory and the script inside it
share a name. `git ls-files` in the gha checkout settled it in one command.
Applying the suggestion verbatim would have documented a nonexistent path
in the entry whose whole purpose is getting someone to run that script.)

**The same check applies to a fix a reviewer describes in prose rather than
in a `suggestion` block, and the sharpest test is the reviewer's own
example.**
A finding that ships a concrete repro case has handed you a test fixture:
run the proposed fix against that very case before adopting it.
A reviewer reasoning about a fix in the abstract can propose one that is
directionally right and still insufficient -- it closes the failure mode
they named while leaving the case they cited broken -- and adopting it
verbatim converts their partial diagnosis into your shipped bug, with the
review thread reading as though the item were settled.
When the proposed fix falls short, prefer eliminating the failure mode
outright over layering another patch onto it, and post the evidence
(the fix applied to their example, and what it still produces) rather than
just asserting it was insufficient.
(gha#318, 2026-07-26: a review correctly found that a heredoc-terminator
regex lacked an end-of-line anchor, and suggested adding one.
Tested against the reviewer's own indented-`EOF` example, the suggested
anchor still truncated the body, because the terminator's leading `[ \t]*`
accepted a space-indented closing line real bash rejects.
Matching whole lines against the tag -- how bash itself ends a heredoc --
removed the whole lazy-quantifier/anchor failure mode instead of narrowing
it; the reply carried the failing output of the suggested form.)

**A reviewer's corrected citation is another factual claim, so verify the
replacement before adopting it.**
The finding can be right: the citation in the PR can name the wrong source.
That does not make the reviewer's proposed source right.
A replacement issue or PR number is a fresh provenance claim, and it needs the
same check as the original citation.
For text provenance, prefer history over word association:
`git log -S "<exact line>" -- <file>` asks which commit introduced the line,
while matching a word in another PR plus a nearby merge time only builds a
story.
Keep the review's conclusion when it is right, but set aside the replacement
when the evidence points elsewhere, and say which query decided it.

- **Do:** verify a proposed replacement citation with the source's own history
  before editing the PR to use it.
- **Do:** use `git log -S "<exact line>" -- <file>` or an equivalent
  provenance query when the question is which PR introduced text.
- **Don't:** adopt a reviewer's corrected issue or PR number because the
  original was wrong.
- **Don't:** use word overlap and same-day timing as a substitute for source
  history.

(Morrison-Lab/ai-config#971 round 2, 2026-08-01: a review correctly found that
PR #955 did not cover a "default nobody chose" case record.
It then proposed #951 as the source because #951's `memories/tools.md` entry
used the word "default" and merged the same day.
That was the wrong default and the wrong file: #951 did not touch
`shared/workflow/metacognitive-monitoring.md`.
`git log -S "An unexamined default" -- shared/workflow/metacognitive-monitoring.md`
identified #947 as the source for the default half, while #955 supplied the
handed-premise half in the same fragment.)

**The highest-yield version of that check: when a comment names an edge case
in its own prose and also supplies a fix, run the fix against that edge
case.**
The bullets above test a suggestion against the code, or against a repro the
reviewer provided.
This tests it against the reviewer's *other paragraph*, and it is the cheapest
of them, because the hazard has already been identified for you --- the
work left is only to check whether the proposed code handles it.

Nothing forces the two halves to agree.
A comment's prose and its suggestion are drafted separately, and a reviewer
who spots an edge case while reasoning about the problem does not necessarily
carry it into the snippet.
So a comment can read as unusually thorough --- it anticipated a failure mode
you had not --- while shipping a fix that falls into exactly it.
That thoroughness is what makes the suggestion persuasive, which is the trap.

Applying it is worse than ignoring the whole finding.
The prose half was right, so the reviewer's authority is real; the snippet
then lands under that authority carrying a defect the same comment already
described, and the thread reads as settled.
Worse still when the defect is one your own corpus documents, since the
review has now talked you out of a standing rule.

Keep the finding and reject the snippet.
Fix it your own way, quote the edge case back, and say plainly why the
suggested form was set aside --- silently deviating from a `suggestion` block
reads as having missed it.

- **Do:** check a suggested fix against every failure mode the same comment
  names, before checking anything else about it.
- **Do:** name the reviewer's own caveat in the reply, so the rebuttal rests
  on their evidence rather than on your say-so.
- **Don't:** let a comment's demonstrated thoroughness transfer to its
  snippet --- they are separate claims.
- **Don't:** discard a finding because its fix is wrong; the half that named
  the hazard usually still stands.

(Morrison-Lab/ai-config#868, 2026-07-30: a review correctly found that
`git merge-base --is-ancestor` prints nothing and answers by exit status, and
its second paragraph noted the command exits 2 or higher when the ref has
been pruned away.
Its suggested `... && echo "ancestor" || echo "not ancestor"` maps that exit
onto the `not ancestor` branch, since `&&` fails on any non-zero status --- so
the fix printed a confident verdict for precisely the broken-check case the
comment itself had raised, which is the shape
[`fail-fast`](../principles/fail-fast.md) names.
A three-arm `case $?` was used instead, reporting `0`, `1`, and `2+`
distinctly.)

**A quieter variant: the suggestion introduces no defect at all, it restates
the line above it --- so applying it deletes coverage while reading as
hardening.**
Every bullet above is about a snippet that would break something, so the
reviewer's authority is the trap and skepticism is the defence.
Here nothing breaks.
The tests still pass, the diff looks like a robustness improvement, and the
comment is *correct about the problem*.
What is lost is the only assertion covering a different property, replaced by
a second copy of one already present a line earlier --- so a two-assertion
test becomes a one-assertion test that still looks like two.

The reason it survives review is that the surviving copy passes, which is
indistinguishable from the fix working.
So the usual after-the-fact check --- run the tests --- cannot detect it, and
neither can CI.
It is a [`challenge-redundant-content`](challenge-redundant-content.md)
finding arriving from the reviewer, which is exactly the direction that makes
deferring feel appropriate.

Watch for the comment citing the neighbour as *support*: "the check on the
line above is already load-bearing for this claim" is the argument against the
replacement, and it reads as an argument for it.
Same structure as the edge-case bullet above --- prose and snippet drafted
separately, disagreeing --- one artifact over.

Compare a suggested predicate against its **neighbours**, not only against
the code it replaces, and evaluate both on real input rather than reasoning
about them; one command decides it, per
[`algorithmatize-checks`](algorithmatize-checks.md).
Then prefer removing whatever made the original fragile over swapping one
fragile sentinel for another.

- **Do:** evaluate the suggested predicate and its neighbours on real input,
  and keep the finding while rejecting the snippet when they coincide.
- **Do:** fix the underlying coupling instead, and say in the reply why the
  suggested form was set aside.
- **Don't:** accept a `suggestion` block that restates an adjacent check ---
  passing tests afterward prove nothing, since the survivor passes for both.
- **Don't:** read a reviewer's own "the line above already covers this" as
  support for their replacement.

(Morrison-Lab/ai-config#896, 2026-07-30: a review correctly called a test's
`"user-invocable" not in body` sentinel fragile, and suggested
`"---" not in body.lstrip()[:3]`.
Evaluated against the real body, that is the same predicate as the
`not body.lstrip().startswith("---")` assertion directly above it --- both
test the first three characters, both returned `True` --- so adopting it would
have left one property checked twice and the other not at all.
A synthetic fixture replaced the corpus coupling instead, plus a third
assertion that body prose *survives* stripping, which neither the original nor
the suggestion covered.
Tracked as #905.)

**A finding can be right, and its fix adequate, while the *reason* it supplies
is too weak to ship --- and in a corpus of rules, the reason is the
deliverable.**
The bullets above all test whether the suggested fix *works*: against the code,
against the reviewer's repro, against an edge case their own prose named.
This one assumes it works, and asks whether the justification handed to you
still holds when someone leans on it.

That distinction is invisible in code and decisive in a rule.
A patch is judged by its behaviour, so a correct patch with a shaky rationale is
merely under-commented.
A `shared/` fragment is judged entirely by whether its reason forecloses the
workarounds, so adopting a weaker reason ships a rule the next reader can talk
themselves around --- while the thread records the item as settled.

The tell is a suggestion that explains *why* something is forbidden in a single
phrase, where the primary source carries a stronger provision.
So ask what the strongest *available* reason is, rather than whether the offered
one is defensible, and name the workaround the weaker reason would have
licensed --- that is what makes the choice checkable rather than a matter of
taste.

- **Do:** read the primary source for the strongest reason before adopting a
  suggested rationale, even when the suggestion's conclusion is right.
- **Do:** say in the reply which reason you took and why the offered one was set
  aside, since deviating from a `suggestion` block silently reads as having
  missed it.
- **Don't:** accept a defensible-sounding mechanism because the conclusion it
  supports is correct.
- **Don't:** treat this as grounds to reject the finding --- the conclusion
  usually stands, and only its reason needs strengthening.

(Morrison-Lab/ai-config#873, 2026-07-30: a review correctly found a `CC-BY-ND`
table row that called verbatim copying allowed and then concluded idea-only with
no bridge.
Its suggested reason, "MIT grants modification rights; ND does not", frames the
conflict as two grants differing in scope --- which licenses the workaround of
keeping the file under its own notice inside the MIT repo, since on that framing
no conflict arises.
SPDX `license-list-data`'s `CC-BY-ND-4.0.txt` §2(a)(1) grants a
**non-sublicensable** license, so the material cannot be re-offered under MIT at
all, which is exactly what vendoring does.
The conclusion was right, and its stated reason stopped short of the provision
that actually forecloses the workaround.)

**And the mirror case: a finding can be wrong on its stated grounds while
still pointing at something real.**
The bullets above check the reviewer's *fix*; this one checks their
*premise*.
A confidently reasoned factual claim -- this pattern is valid, that value is
in range, this call is safe -- invites one of two lazy responses: accept it
because it sounds authoritative, or dismiss the whole item once you notice
the claim is false.
Both lose information, because a reviewer usually arrives at a wrong premise
while looking at something that genuinely bothered them.

So reproduce the claim before answering it, and answer the concern
separately from the premise.
When the premise turns out to be false, say so with the command and its
output rather than by assertion, and then address what prompted it anyway --
a reader who tested your example and got a different result has a real
problem even if their explanation of it was wrong.
Expect the corrected mechanism to be more useful than the original text:
a premise worth disputing usually sits on something you had not fully
explained.
(ai-config#756, 2026-07-28: a review held that `[\x{2014}]` is valid PCRE
and so could not produce the "code point value too large" error the fragment
described, and proposed an out-of-range `[\x{110000}]` instead.
Running it showed the original failing exactly as written -- the cause is
the locale, since PCRE in non-UTF mode rejects any `\x{}` above `0xFF`, and
the same command succeeds under `LC_ALL=C.UTF-8`.
The proposed replacement would have been worse, failing unconditionally and
hiding that environment-dependence, which is the whole reason the swallowed
error is dangerous.
The reviewer's actual worry -- that a reader might not reproduce it -- was
right, and sharper than stated.)

**A third direction, which evades the verification reflex rather than lacking
a rule: agreeing with a finding and then escalating it.**
The bullets above check the reviewer's *fix*, and the one above checks their
*premise*.
Both assume you are deciding whether the finding is right.
This is the case where it is right, and correctly scoped, and you tell the
reviewer it understated the problem.

The **obligation** is not new, and claiming otherwise would overstate this
entry.
[`metacognitive-monitoring`](metacognitive-monitoring.md) already requires
verifying a finding's particulars before restating them as fact, and its
**scope** claim type already governs an assertion of your own about how wide a
defect is, telling you to check the population rather than the sample that
came to mind.
An escalation is nothing but a new particular of exactly that kind: a wider
scope, a bigger count, one more failing case.
So it lands squarely in the class that fragment names as least dependable, and
it does so where the reviewer's credibility will carry it.

What is new is the **trigger**.
Both of those rules fire on an act you recognize as asserting something, and
agreeing does not present as one.
Rebutting is adversarial and prompts you to verify.
Extending is agreement wearing extra diligence, and agreement is not a thing
anyone verifies.
So the rule is already there and nothing calls it, which is how the escalation
ships under the reviewer's authority with less scrutiny than a rebuttal would
have got.

Hold an escalation to the standard a rebuttal gets.
Measure with an instrument covering the whole scope your escalation claims,
not merely the narrower scope the finding covered, and say which instrument
that was.
Do not read the finding's narrowness as a bound on the reviewer's instrument.
A reviewer can inspect a whole field set and report only the member that is
broken, so a one-field finding can rest on a five-field probe.
The instrument you need may therefore be the reviewer's own, run without
whatever narrowed your view of it, rather than a new and wider one.
When the escalation turns out to be wrong, correct it on the thread that
carried it, not only in a later round's summary.
A reader who saw "it is worse than you reported" has no other way to learn
that it was not.

- **Do:** verify an escalation against the full scope it claims, which is
  wider than the scope the finding reported, and which the finding's own
  instrument may already cover.
- **Do:** post the correction to the thread that carried the escalation.
- **Don't:** treat agreeing-and-extending as exempt from the checks a rebuttal
  gets, since agreement suppresses the reflex that disagreement triggers.
- **Don't:** report a finding as understated on a measurement you have not
  shown covers the whole field set.

(Morrison-Lab/ai-config#1056, 2026-08-02: Copilot found that the
`LIST_SECRETS` row promised `created_at`, which `gh secret list` does not
expose, and that finding was correct and correctly scoped to one field.
The session ran `gh secret list --repo <owner>/<repo> --json 2>&1 | head -3`,
read the two field names that survived its own truncation, and replied that
the CLI failed on two of three fields rather than one, writing that into
`tool-mappings.yml` as measured fact.
On gh 2.96.0 that usage message lists five fields: `name`,
`numSelectedRepos`, `selectedReposURL`, `updatedAt`, and `visibility`.
A usage line plus the first two of those is exactly what `head -3` returns, so
`updatedAt` was reachable all along and only `created_at` was not.
A later round caught it, and the correction had to be posted to the original
thread.

Note which instrument was the wider one, because it is the reverse of what
escalating assumes.
The reviewer's report named one field and its instrument showed all five,
while the escalation named three fields on a view of two.
The defect was truncating a full-scope instrument rather than choosing a
narrow one, which is why the remedy is coverage of your own claim rather than
a probe wider than the reviewer's.)

**When a finding cites a source, read the cited source before reproducing
anything -- it is the cheaper instrument, and it is the one that can show the
finding backwards rather than merely unsupported.**
The bullet above says to reproduce the claim.
That is right, and it is the second thing to do when a citation is on the
table, because reproduction tests the *behavior* while the citation tests the
*reasoning*, and only the second can catch a finding whose own evidence
contradicts it.
A citation is also the most persuasive part of a review and the least likely
to be checked: a linked changelog entry reads as settled fact, so the finding
inherits authority it never earned, and a one-click `suggestion` block turns
that borrowed authority into an applied edit.

Grep the cited document for the mechanism the finding names.
One command usually decides it, which makes this an
[`algorithmatize-checks`](algorithmatize-checks.md) case rather than a
judgment call, and a fabricated mechanism produces a clean zero-hit result
that is hard to argue with.
Then quote the entry in the reply rather than paraphrasing the disagreement,
and reproduce the behavior as the independent second leg.

Do not stop at winning the point.
A finding that misread a source usually did so because the claim it
questioned had nothing checkable next to it, so fold the citation into the
file itself, per [`fully-clean`](fully-clean.md)'s note that a fresh review
run re-derives from scratch and will not read the thread.
(ai-config#762, 2026-07-28: a review held that
`htmlwidgets::saveWidget(selfcontained = TRUE)` no longer needs pandoc,
citing htmlwidgets 1.6.0 as having "switched to `base64enc::dataURI()`", and
supplied a suggestion block deleting the `rmarkdown::pandoc_available()`
gate.
`grep -inE 'pandoc|base64'` over that NEWS file returned six pandoc hits and
zero base64 hits, and the 1.6.0 entry says the path "now uses the
`{rmarkdown}` package to discover and call pandoc" -- so the citation
established the opposite of the finding, and incidentally made the gate the
*same lookup* htmlwidgets performs rather than a proxy for it.
Applying the suggestion would have removed the only warning before a hard
error, in the one step that exists for running headless.
The reviewer accepted the rebuttal on the next round and called its own prior
claim a hallucination.)

**When a reviewer hedges a finding because it depends on code it cannot
see, check whether *you* can see it --- the hedge is an invitation, not a
verdict.**
Automated reviewers work from the diff, so a finding that turns on a
reusable workflow, a dependency's internals, or another repo's behavior
arrives with language like "moderate rather than high confidence",
"depends on behavior not visible in this diff", or "worth the author
confirming intent".
That hedge is a fact about the *reviewer's* visibility, not about how
likely the finding is.
You frequently have access it lacks: the repo cloned locally, a pinned
dependency vendored in, or permission to fetch the source.

Reading it converts a maybe into a settled yes or no, and that changes
the disposition.
Confirmed, it earns a fix or a precisely-scoped follow-up issue with the
mechanism recorded; disproved, it earns a Rebut with evidence instead of
a vague "I think this is fine".
Either way the next reader is spared re-deriving it.
Quote the specific lines you checked, since a follow-up issue that merely
repeats the reviewer's hedge is barely more useful than the review
comment it came from.

(`UCD-SERG/serodynamics#274`, 2026-07-28: a review flagged possible
duplicate review dispatch at moderate confidence, explicitly because the
reusable workflow in `d-morrison/gha` was not visible to it.
That repo was cloned locally.
Reading both matchers showed the reusable fires on `@claude[[:space:]]+review`
and the local job on a punctuation-tolerant superset, so the plainest
phrasing --- `@claude review` --- matches both and dispatches twice.
The follow-up issue could then record the exact overlap table and note
that the upstream gap motivating the local job had since been closed,
making "broaden upstream, delete the local job" a real option.)

**Timestamp the evidence before rebutting a finding with it --- during a live
incident, a log from twenty minutes ago describes a different system.**
The bullets above all say to verify a finding rather than accept it, and they
assume verification is a fixed target: read the source, run the command,
reproduce the case.
That assumption quietly fails while something is actively breaking, because
the evidence you gather is a *measurement*, and measurements expire.
Re-reading an existing CI log feels like verification --- it is concrete, it
is specific, it is right there --- but it only tells you what was true when
that job ran.

The tell is a rebuttal whose evidence you did not generate yourself in this
turn.
A log you fetched, a check-run conclusion you read, a status you were told
about: each carries a timestamp, and the question is whether anything could
have changed since.
When the finding is *about* an outage, a migration, a permission change, or
anything else in flight, the answer is almost always yes.

So prefer evidence you can regenerate now over evidence you can only cite.
Re-running the failing thing is usually cheap and settles it outright --- and
in the best case it produces the cleanest possible proof, two attempts of the
same run on the same commit disagreeing, which no amount of reading could
have given you.
When regenerating is genuinely not possible, say how old the evidence is in
the rebuttal itself, so the reader can weigh it.

This matters more than an ordinary wrong rebuttal because of who it lands on.
Telling an author their diagnosis is contradicted by the logs is a strong
claim that invites them to stop investigating.
Getting it wrong can stall a correct fix for the exact bug still breaking
everything.
(gha#351, 2026-07-28: a PR correctly diagnosed that Actions had stopped
resolving `uses:` after a repo transfer.
Its premise was disputed on the strength of two run logs showing the
workflow resolving fine --- logs from 45 and 30 minutes before the PR was
opened, spanning the cutover.
Re-running one of those very workflows reproduced `startup_failure`
immediately, and the retraction had to be published in the same thread.)

**A finding built on a *negative* result -- "I searched and it isn't there"
-- is only as strong as the paths that were searched, and the search scope
is the part reviewers state loosest.**
The bullets above all check a reviewer's positive evidence: the suggested
literal, the proposed fix, the cited source.
A negative result invites none of that scrutiny, because there is nothing
to look up: the claim is that looking up would fail.
It also arrives sounding the most settled of any finding -- "no file or
heading with that title anywhere" reads as exhaustive, and the reader's
natural move is to accept it and edit.

So read the search itself rather than the conclusion.
Ask which paths were actually covered, and whether the obvious location is
among them.
One command usually settles it, which makes this an
[`algorithmatize-checks`](algorithmatize-checks.md) case rather than a
judgment call -- and note the reviewer's own tooling may have failed the way
[`fail-fast`](../principles/fail-fast.md)'s hand-check section describes,
matching too narrowly against text that was wrapped or reformatted.

When it turns out the thing does exist, name the gap rather than only the
correction: which paths were searched, where it actually lives, and why the
two did not overlap.
That is what stops the same search being re-run the same way.
And check whether the finding still points at something real, per the mirror
case above -- an unresolvable-looking citation is often a genuinely
under-specified one.

- **Do:** ask which paths a negative finding actually searched, and check the
  obvious location yourself before editing anything.
- **Do:** name the gap when the thing does exist -- paths searched versus
  where it lives -- so the same search is not re-run the same way.
- **Don't:** accept "it isn't there anywhere" as settled because it is stated
  more confidently than a positive finding would be.
- **Don't:** discard the finding once its negative result is disproved -- the
  thing it tripped over is often a real ambiguity.

(`Morrison-Lab/gha#338`, 2026-07-28: a review reported a cited section as
nonexistent, having "checked ai-config's full tree (`shared/workflow/*.md`,
`skills/`, `codex-skills/`)".
The heading was an H2 in that repo's **root** `CLAUDE.md`, the one directory
those three paths skip.
The reviewer had even found the phrase in `shared/workflow/fully-clean.md`
and read it as pointing at a *consuming* repo's `CLAUDE.md`.
The rebuttal carried the one-line grep; the underlying point was real
anyway, since citing a section title without naming its file is what sent
the search to the wrong directories, so the citation was fixed to name and
link the file.)
