Skip to content

fix(claude-code-review): post only the reviewer's last complete draft (closes #805) - #808

Merged
d-morrison merged 8 commits into
mainfrom
fix/805-post-last-complete-review
Sep 2, 2026
Merged

fix(claude-code-review): post only the reviewer's last complete draft (closes #805)#808
d-morrison merged 8 commits into
mainfrom
fix/805-post-last-complete-review

Conversation

@d-morrison

Copy link
Copy Markdown
Collaborator

Closes #805.

claude-code-review.yml posted a review comment carrying three complete
drafts, each with its own verdict heading and structured-review-data block
(Morrison-Lab/ai-config#2966, run 33594599768).
The cause is gha#710's span rule: when more than one assistant block carries
a verdict line, the posted text runs from the first such block to the last,
which is right for a review split across blocks and wrong for a reviewer that
redrafts its whole final message.

What changes

  • A complete draft carries a verdict heading; the gha#710 follow-up tail
    writes a plain verdict line or the word in prose.
    When more than one block carries a heading, the posted span now starts at
    the last such block and still runs to the last verdict-bearing block, so
    superseded drafts are dropped and a trailing "verdict stands unchanged"
    tail is kept.
    One heading leaves gha#710's behaviour untouched.

Tests

  • verdict-redrafted-thrice.json (three drafts plus an instrument re-run
    between the first two) must post the third draft and must not post the
    first; disabling the new branch turns it red.
  • assert_pass now holds every posted review to at most one verdict
    heading, as a shape check on the extraction rather than a verdict parse.

Originally stacked on #807; rebased onto main after that merged.

🤖 Generated with Claude Code

…closes #805)

gha#710 widened the posted text to the span from the first verdict-bearing
block to the last. A reviewer that redrafts its final message, each draft a
complete review with its own verdict heading, then gets every draft
concatenated (three reviews, three verdicts, two Stopping-Point lines on
Morrison-Lab/ai-config#2966).

A complete draft carries a verdict HEADING; the gha#710 follow-up tail only
writes a verdict line or the word in prose. So when more than one block
carries a heading, the span starts at the last such block and still runs
to the last verdict-bearing block. One heading is unchanged.

verdict-redrafted-thrice.json pins it (must-contain the third draft,
must-not-contain the first); assert_pass holds every posted review to at
most one verdict heading. Reverting to the span rule turns the fixture red.

Stacked on #807 (fix/804-quota-notice-reason).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@d-morrison

Copy link
Copy Markdown
Collaborator Author

Claude Code CLI (local session) is working on this — please hold off on pushing to this branch until I'm done.

Posted by Claude Code (AI agent) --- not written by a human.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new verdict-heading invariant in run-fixture-tests.sh can miscount headings inside fenced code blocks (and the gha#805 commentary should be aligned with what the regex actually matches).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes the claude-code-review posting logic so that when a reviewer redrafts its final message multiple times (each draft containing its own ### Verdict heading), only the last complete draft is posted, avoiding concatenated duplicate reviews.

Changes:

  • Update verdict-span extraction in check-review-execution.sh to prefer the last verdict-heading block when multiple verdict headings are present (gha#805), while preserving gha#710 behavior for split-across-blocks reviews.
  • Add a new execution-output fixture (verdict-redrafted-thrice.json) and extend run-fixture-tests.sh assertions to pin the new behavior and guard against reintroducing multi-draft concatenation.
  • Document the gha#805 failure mode and fix rationale in CLAUDE.md, and add a changelog fragment.
File summaries
File Description
CLAUDE.md Documents the gha#805 failure mode and the “use last verdict heading” rule.
changelog.d/post-last-complete-review-draft.fixed.md Records the user-facing behavior change in the changelog fragments system.
.github/workflows/scripts/check-review-execution.sh Adjusts extraction logic to post only the reviewer’s last complete draft when multiple verdict headings exist.
.github/workflows/scripts/tests/run-fixture-tests.sh Adds fixture expectations and an invariant asserting at most one verdict heading is posted.
.github/workflows/scripts/tests/fixtures/verdict-redrafted-thrice.json New fixture simulating three complete redrafted reviews.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +435 to +443
# gha#805, as an invariant over every posted review rather than one
# fixture: a comment carries at most ONE verdict heading. A second heading
# means two complete drafts were concatenated, whichever fixture produced
# them. The gha#710 tail writes `Verdict:` without a heading, so it does
# not count, and neither does a heading quoted inside a fenced block --
# this is a shape check on our own extraction, not a verdict parse.
local headings
headings="$(grep -ciE '^[[:space:]>*_-]*#{1,6}[[:space:]]*verdict' "$posted_file" || true)"
if [[ "$headings" -gt 1 ]]; then
Comment on lines +637 to +641
# HEADING form: a complete draft carries `### Verdict` (or any `#` heading
# naming it), while the gha#710 follow-up tail only writes a `Verdict:` line
# or the word in prose ("my verdict stands unchanged"). So when more than one
# block carries a verdict HEADING, the span starts at the LAST such block: the
# earlier drafts were superseded by their author, and the tail after the last
d-morrison and others added 3 commits September 2, 2026 00:52
… round 1)

A heading inside a fenced code block or a blockquote is quoted, not
authored. With the bare regex a later block that merely showed the heading
shape, or blockquoted the previous verdict, read as a fresh draft, and the
span then started there and dropped the entire real review -- the failure
gha#710 exists to prevent. Both the jq detector and the bash invariant now
skip fenced and blockquoted lines (no awk interval expression, per the
mawk rule).

- verdict-then-quoted-heading.json pins that a quoted heading is not a draft.
- verdict-redrafted-thrice.json gains a tail block after the last draft and
  a second must-contain needle, so tail retention is pinned.
- Prose names the tail shape the regex actually matches (a line-start
  verdict line), not a mid-sentence mention.

Three mutations turn a named case red: disabling the multi-heading branch,
narrowing the span end to the last heading block, dropping the exclusion.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…iew round 2)

A fence closes on a run of the same character at least as long as the
opener (CommonMark, as strip-non-invoking-markup.sh implements); the first
draft closed on any fence line, so a backtick fence holding a tilde line
leaked the heading after it. Both the jq detector and the awk invariant now
track the opener. An unclosed fence runs to the end of its block, which is
how GitHub renders it too, so that case is documented rather than
special-cased.

verdict-then-mismatched-fence.json pins it; closing on any delimiter turns
it red.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… its run (review round 3)

CommonMark: a closing fence line may hold nothing but whitespace after the
delimiter run; a run followed by text is fence content. Both the jq
detector and the awk invariant now require that, matching
strip-non-invoking-markup.sh. verdict-then-trailing-text-closer.json pins
it; dropping the check turns it red.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@d-morrison

Copy link
Copy Markdown
Collaborator Author

Review round on 183d0a52, dispositions at 907ab45e:

  1. Addressed. Heading detection now counts only authored headings: fenced and blockquoted lines are skipped in both the jq detector and the bash invariant, with CommonMark fence closing (same character, run at least as long as the opener, whitespace-only after it). verdict-then-quoted-heading.json, verdict-then-mismatched-fence.json, and verdict-then-trailing-text-closer.json pin the three shapes; each has a confirmed red mutation.
  2. Addressed. The invariant's comment and code agree now; the awk mirrors the jq exclusion and carries no interval expression.
  3. Addressed. verdict-redrafted-thrice.json gained a tail block after the last draft plus a second must-contain needle; narrowing the span end to the last heading block turns it red.
  4. Addressed. Prose names the tail shape the regex matches (a line-start verdict line), not a mid-sentence mention.

One adjacent point raised by the local pre-push reviewer was rebutted: an unclosed fence inside a later redraft hides that draft's own heading and falls back to the gha#710 span rule. That is CommonMark's semantics and GitHub renders the same text the same way, so it is documented in the code comment and CLAUDE.md rather than special-cased.

Posted by Claude Code (AI agent) --- not written by a human.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new “one authored verdict heading” invariant in run-fixture-tests.sh uses a broader awk match than the extractor’s verdict\b rule, which can produce false failures unless aligned.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Lite

}
fence != "" { next }
/^[ \t]*>/ { next }
tolower($0) ~ /^[ \t]*#+[ \t]*verdict/ { n++ }
…review round 3)

CommonMark's third literal construct, the indented code block, needs no
state: a heading may be indented by at most three spaces, so a line at
four columns or a tab is never a heading, whether it is code or a lazy
paragraph continuation. Both the jq detector and the awk invariant now say
so. verdict-then-indented-heading.json pins it; widening the indent back
to any whitespace turns it red.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@d-morrison

d-morrison commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Round on 907ab45e, dispositions at 1373a976:

  • Claude, indented code blocks: Addressed. A heading may be indented by at most three spaces (CommonMark), so the heading test now says so in both the jq detector and the awk invariant, and a four-column or tab-indented line is never a heading whether it is code or a lazy paragraph continuation. No state machine was needed. verdict-then-indented-heading.json pins both shapes; widening the indent back to any whitespace turns it red.
  • Copilot, fence-aware invariant and comment alignment: Addressed in the previous push (907ab45e): the awk mirrors the jq's fence and blockquote handling, and the comment describes what the code does.

Posted by Claude Code (AI agent) --- not written by a human.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new fence detectors treat leading tabs as valid ≤3-column indentation, which is inconsistent with CommonMark’s tab expansion and can misclassify fenced blocks/headings in edge cases.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 2
  • Review effort level: Lite

def authored_heading:
( split("\n")
| reduce .[] as $l ({fence: "", flen: 0, out: []};
( [ $l | capture("^[ \\t]{0,3}(?<run>`{3,}|~{3,})(?<rest>.*)$") ] | first ) as $f
Comment on lines +481 to +482
match($0, /^[ \t]?[ \t]?[ \t]?(```+|~~~+)/) {
run = substr($0, RSTART, RLENGTH); sub(/^[ \t]+/, "", run)
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

…oundary (review round 4)

A tab is four columns in CommonMark, so a tab-led backtick line is indented
code rather than a fence; the jq capture and the awk now allow spaces only
in the three-column indentation, and the awk spells the jq's word boundary
as a trailing class so a "Verdicts" heading counts in neither (Copilot on
gha#808). verdict-redraft-after-tab-fence.json pins the first; a plural
heading in verdict-then-quoted-heading.json pins the second. Both mutations
turn a named case red.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@d-morrison

Copy link
Copy Markdown
Collaborator Author

Round on 1373a976, dispositions at 4e51a264:

  • Copilot, tabs in the fence indentation: Addressed. A tab is four columns, so both the jq capture and the awk now allow spaces only in the three-column allowance; a tab-led backtick line is indented code and opens or closes no fence. verdict-redraft-after-tab-fence.json pins the extractor half and verdict-then-tab-inside-fence.json the awk half; each has a confirmed red mutation.
  • Copilot, awk word boundary (earlier inline): Addressed. The awk spells the jq's word boundary as a trailing class; a plural heading added to verdict-then-quoted-heading.json pins it.
  • Claude, round on 1373a976: Ready for merge, no findings. Carried forward unchanged.

Posted by Claude Code (AI agent) --- not written by a human.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new verdict-heading invariant in run-fixture-tests.sh can miscount non-CommonMark headings (7+ #) as headings, risking false test failures and diverging from the extractor’s #{1,6} logic.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

# code or a lazy continuation, never a heading (gha#808 review round 3).
# The trailing class is the word boundary the jq spells verdict\b, so
# a "Verdicts" heading counts in neither (Copilot on gha#808).
tolower($0) ~ /^ ? ? ?#+[ \t]*verdict([^a-z0-9_]|$)/ { n++ }
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

… (review round 5)

CommonMark: an ATX heading has one to six hashes followed by a space or
tab; seven hashes, or hashes run into the word, are paragraph text. The jq
test and the awk invariant now both say so, the awk by run length rather
than an interval expression. verdict-then-quoted-heading.json gains both
non-heading shapes; lifting the limit in either place turns it red.

Prose corrected: verdict-then-tab-inside-fence.json pins both halves, the
extractor's silent drop of the real review and the awk's double count, not
the awk alone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@d-morrison

Copy link
Copy Markdown
Collaborator Author

Round on 4e51a264, dispositions at 0ce7b71a:

  • Claude, prose mischaracterized verdict-then-tab-inside-fence.json: Addressed. CLAUDE.md and the test-table comment now say the fixture pins both halves: a tab-admitting extractor un-fences the quoted heading and drops the real review (verified by running the 1373a976 extractor against the fixture, which posts a span without the real analysis), and a tab-admitting awk counts two headings in the correct span.
  • Copilot, awk accepts seven-plus hashes: Addressed. The awk measures the hash run (no interval expression) and, like the jq, requires at least one space or tab after the hashes; ####### Verdict and ###Verdict in verdict-then-quoted-heading.json pin both limits in both places, with confirmed red mutations for each.

Posted by Claude Code (AI agent) --- not written by a human.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The extraction change is narrowly scoped, well-documented, and backed by targeted fixtures plus an invariant that guards against reintroducing multi-draft concatenation.

Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

…ound 6)

The awk's trailing word-boundary class has its own confirmed red mutation
on verdict-then-quoted-heading.json and was missing from the enumeration.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@d-morrison

Copy link
Copy Markdown
Collaborator Author

Round on 0ce7b71a, disposition at be8f3c23:

  • Claude, mutation list omits the awk word-boundary case: Addressed. The enumeration now reads nine and names it.
  • Copilot: Approval recommended. Carried forward.

Posted by Claude Code (AI agent) --- not written by a human.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Claude finished reviewView run


Confirmed — this round's entire delta is exactly this one 6-line documentation-only change, and nothing else. My review is complete.

Review of #808 — round 7

Range reviewed: 0ce7b71a..fbdf0431 (one commit, be8f3c2, "docs(claude-code-review): list the ninth confirmed mutation (review round 6)", plus a no-op merge commit), touching only CLAUDE.md (3 insertions, 3 deletions).

This commit directly resolves the sole remaining finding from the prior review round (issuecomment posted 2026-09-02T10:01:21Z): the "Seven mutations" (by then already "Eight" after an intervening commit) enumeration was missing the confirmed-red mutation of dropping the awk's trailing word-boundary class (verdict([^a-z0-9_]|$) → bare verdict). This commit bumps the count from "Eight" to "Nine" and appends "dropping the awk's trailing word-boundary class" to the enumerated list at CLAUDE.md:2067-2072.

Verification performed

I didn't just read the diff — I re-ran the mutation myself against the checked-out code:

  1. Full fixture suite passes as committed: all 46 fixtures green (bash .github/workflows/scripts/tests/run-fixture-tests.sh).
  2. Reproduced the exact mutation the new prose claims: reverted run-fixture-tests.sh's awk regex from verdict([^a-z0-9_]|$) to bare verdict. Result: exactly one fixture fails — verdict-then-quoted-heading.json — with diagnostic posted review carries 2 verdict headings (gha#805), confirming the "### Verdicts of prior rounds" line in that fixture now spuriously counts as a second authored heading. This matches the prior round's own finding precisely.
  3. Restored the file; confirmed the suite returns to 46/46 green and git status shows no stray changes.
  4. Counted the enumerated list in CLAUDE.md:2067-2072: exactly nine items, consistent with "Nine mutations."
  5. Re-confirmed the round-5 mischaracterization finding (about what verdict-then-tab-inside-fence.json actually pins) remains correctly worded at CLAUDE.md:2060-2064 — that fix predates this delta and needed no further change.
  6. Ran check-new-line-breaks (diff-scoped, NLB_GLOBS='*.md *.qmd') and markdownlint-cli2 pinned to the version lint-markdown/package.json declares (0.23.0) against CLAUDE.md — both clean.

What checked out

  • The count-and-enumeration fix is accurate and empirically verified, not merely plausible.
  • No other files changed in this round; no versioning-doc-sync obligations apply (internal script/doc only).
  • No AI-prose tells, no forward references, no broken crossrefs introduced by the three-line change.

No new findings.

Verdict

Ready for merge. This round's fix (be8f3c2) is a minimal, accurate documentation correction that directly closes the last outstanding finding from the prior review round. I independently reproduced the mutation it describes and confirmed it turns exactly the claimed fixture red, with the enumerated count now internally consistent (nine items, "Nine mutations"). All 46 fixtures pass, and the changed prose is clean under this repo's own line-break and markdownlint checks.

Structured Review Data (JSON)
{
  "schema_version": "1.0",
  "reviewer": "claude",
  "commit_sha": "fbdf043118ef90fc98f6baa4df1ee73820094626",
  "verdict": "CLEAN",
  "findings": []
}

Reviewed commit: be8f3c2

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

💰 Cost: $1.3568 (review) — run

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The extraction change is narrowly scoped, preserves prior behavior for the split-across-blocks case, and is backed by multiple discriminating fixtures plus a cross-fixture invariant.

Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@d-morrison
d-morrison merged commit 0b4859d into main Sep 2, 2026
66 checks passed
@d-morrison
d-morrison deleted the fix/805-post-last-complete-review branch September 2, 2026 10:39
@d-morrison
d-morrison requested a lite review from Copilot September 2, 2026 14:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The extraction change is narrowly scoped, well-justified by the documented failure mode, and backed by targeted fixtures plus a global shape invariant to prevent regression.

Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new jq capture(...) usage in check-review-execution.sh will error on non-matching lines (breaking extraction) unless made optional (e.g., capture(...) ? / try).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

# Spaces only in the indentation allowance: a tab is four columns in
# CommonMark, so a tab-led fence line is indented code, not a fence
# (Copilot on gha#808).
( [ $l | capture("^ {0,3}(?<run>`{3,}|~{3,})(?<rest>.*)$") ] | first ) as $f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

claude-code-review: the posted review comment carried the whole review three times, with three Verdict sections

2 participants