Skip to content

feat(pr-review-cli): count byte-identical changed files as read once - #947

Merged
kyle-sexton merged 8 commits into
mainfrom
feat/review-dedupe-identical-files
Nov 1, 2026
Merged

kyle-sexton merged 8 commits into
mainfrom
feat/review-dedupe-identical-files

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

No related issue: a review-lane defect on melodic-software/claude-code-plugins#11791, assigned by the GitHub Support hub.

Summary

claude-code-plugins#11791 changed 19 files: 3 real files, 8 byte-identical copies of one shared library, and 8 changelog fragments. The review read 11 of them, correctly skipping the 8 identical copies. It then failed as partial, because coverage counts files_read against every changed file, so the PR stalled.

Fix

In all three credential legs:

  • The full-diff step groups changed, non-deleted files by post-image blob SHA (git diff --raw --no-abbrev) and writes .claude-lane/identical.txt, one group per line with the first path followed by its copies. It outputs copies.
  • When copies exist, the prompt tells the model, framing the paths as untrusted data, to read each group's first path once and to leave the copies out of files_read.
  • The coverage check credits the copies after capping the model's count: read = min(read, changed - copies) + copies.

It fails closed: an unreadable listing, a planted identical.txt, or a malformed count gives copies=0, which is today's behavior. Incremental and chunked reviews get no dedupe. For incremental reviews, the groups would come from the whole PR, not the scope. For chunked reviews, the splitter lives in claude-code-plugins and does not keep a group in one chunk.

Verification

  • node --test .github/scripts/pr-review-cli.test.cjs: 53/53, including a real-git grouping fixture and cases for the prompt line and the coverage credit (11 read + 8 copies = complete; 10 + 8 = partial; over-reports are capped).
  • node --test .github/scripts/pr-review-cli-api-calls.test.cjs: 10/10.
  • biome ci on .github/scripts: clean.

Related

🤖 Generated with Claude Code

kyle-sexton and others added 6 commits October 31, 2026 20:22
A diff in scope over one review's 300000-byte read budget was reviewed
by one model step and always concluded partial. Each model leg now splits
it at file boundaries, each file whole, into at most 4 chunks with
claude-code-plugins' .github/scripts/pr-review-main/split-diff.py, read
from that repository's default-branch tip with the review plugin, and
reviews each chunk in its own run-claude-cli step, in order.

- Split step: runs only over the budget, into a chunk directory that must
  be new, refuses a symlinked diff, and outputs the chunk count and each
  chunk's file count. A refused or failed split reviews unsplit, which
  the extract step keeps partial.
- Compose: one prompt per chunk naming its patch and the manifest, with
  the summary and comment caps divided by the chunk count so the combined
  review still fits one environment string.
- Chunk k runs only when chunk k-1 succeeded and handed nothing on. The
  job's handoff, lane class and outcome report follow the last chunk that
  ran; a credit stop in any chunk is recorded.
- Extract: complete only when every chunk's step succeeded and returned a
  well-formed review; findings, comments and files read are the chunks'
  sums, each chunk's files read capped at the files the job counted in
  it, and each chunk file must be a regular file within the budget. A
  failed, skipped or verdict-less chunk is incomplete, so the status check
  is never success before every chunk returns.
- Job limits grow by the three extra chunk steps, their timing steps and
  the split step.

Refs: melodic-software/claude-code-plugins#11575

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plugin step now also checks out .github/scripts/pr-review-main for
split-diff.py; collect-settled-threads.test.cjs pinned the old set.

Refs: melodic-software/claude-code-plugins#11575

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A head that commits .claude-lane/chunks failed the split step silently;
it now warns and takes one review, which stays partial when over budget.

Refs: melodic-software/claude-code-plugins#11575

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A chunked review was told to report any reference to a file another chunk
holds, which flagged valid caller-helper pairs split by the partition. The
model now reads the other file's changes as context and reports only a
verified incompatibility, in all three credential legs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nk-oversize

# Conflicts:
#	.github/scripts/pr-review-cli.test.cjs
#	.github/workflows/pr-review-cli.yml
A pull request that syncs one file into many places changed files the
model rightly reads once, so its files_read fell below the files in
scope and the review was published as partial.

The full-diff step groups changed, non-deleted files by post-image blob
(git diff --raw --no-abbrev) into .claude-lane/identical.txt and outputs
the number of extra copies. The prompt names the file as untrusted data
and tells the model to read each group's first path and not count its
copies; the extract step credits the copies, capping the model's count
first. Only one review of the whole pull request gets the note and the
credit: incremental and chunked reviews keep copies at 0. A planted
file or any failure groups nothing. All three legs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from fix/claude-review-chunk-oversize to main November 1, 2026 06:36
…entical-files

# Conflicts:
#	.github/workflows/pr-review-cli.yml
@kyle-sexton
kyle-sexton marked this pull request as ready for review November 1, 2026 14:39
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-01T14:41:59.669969Z efefa63 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread .github/workflows/pr-review-cli.yml Resolved
Comment thread .github/workflows/pr-review-cli.yml Resolved
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

claude-security-review reviewed 1d31b87c06eb45d53f5a4d24e6092ece6f2ee44d: 0 finding(s), 0 as inline comments.

No security issues found in the incremental change since efefa63 (.github/workflows/pr-review-cli.yml, .github/scripts/pr-review-cli.test.cjs).

What I read. The full-diff step now groups a pull request's identical changes. It keys on both modes and both blobs from git diff --raw, and it takes only plain modifications of regular files (:100644 or :100755, status M, exactly two path fields, unquoted path). Adds, deletes, renames, copies, type changes, symlinks and submodules never group. A path matching a hold-paths glob is also excluded. The change repeats in all three model legs, along with the compose-prompt wording and the HOLD_PATHS env wiring.

Checks.

  • Glob matching. The new awk glob-to-regex translation matches the scope step's globToRegExp (.github/workflows/pr-review-cli.yml:540-558) rule for rule: **/, **, *, ?, the same escape set, and anchoring. A trusted-path file that the TRUSTED PATHS prompt line names can't be grouped as a skipped copy.
  • Hold-paths input. HOLD_PATHS is passed through env:, never interpolated into the script. A pull request's author does not control it. The awk program takes it from ENVIRON, so there is no injection into the script body.
  • Untrusted paths. Paths in identical.txt come from the diff and are still declared untrusted data in the prompt. The plant guard (! -e and ! -L) is unchanged.
  • Direction of failure. The change only narrows what can be treated as a skipped copy. Renames, mode changes, convergent post-images and trusted paths used to group, or could have, and now get a full read. A step failure still groups nothing.
  • Empty hold-paths. With the default empty hold-paths, trusted files can still group. No trusted path is passed to the review in that case, so no scrutiny is lost, but the prompt line "No trusted path is ever a copy" then has nothing behind it. This is a wording imprecision only, not a finding.
  • Tests. The test changes only add cases for the new exclusions. They touch no trust control.

I did not run the awk program, because the sandbox refused the awk-version check.

No trust-control widening, secret or token exposure, or untrusted text reaching a credentialed lane. No manifest items were hit.

critical-findings: 0

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

claude-review reviewed 1d31b87c06eb45d53f5a4d24e6092ece6f2ee44d: 0 finding(s), 0 as inline comments.

Incremental review of 1d31b87 (since efefa63)

No findings.

I reviewed .github/workflows/pr-review-cli.yml and .github/scripts/pr-review-cli.test.cjs from .claude-lane/incremental.diff.

  • Grouping key. The raw line :<mode> <mode> <sha> <sha> M is now the key. Two files group only when both blobs and both modes match. The filter requires NF == 2, status M, and mode 100644 or 100755 on both sides. That excludes adds, deletes, renames, copies, type changes, symlinks, submodules and quoted paths.
  • Hold-paths exclusion. The awk glob-to-regex translation matches the scope step's globToRegExp: **/, **, * and ? are handled the same way, and the same metacharacters are escaped. A match on any path leaves the group, whatever its position. The JS trim is slightly broader than the awk trim (space, tab, CR). That only matters for exotic whitespace and fails toward fewer exclusions, not a missed one.
  • Wiring. HOLD_PATHS is passed to the full-diff step in all three credential legs. The comment and prompt text edits are identical across the three legs.
  • Tests. The fixture covers a different pre-image converging on one post-image, a truncation to empty, a mode change, an add, a delete, and a hold-paths match both leading and mid-group. It also asserts the HOLD_PATHS env wiring.

I could not run node --test .github/scripts/pr-review-cli.test.cjs, because the sandbox denied the command. These conclusions come from reading the diff and the scope step's glob code. The PR's claimed test results are the author's and I have not checked them.

PR context: the body states no issue to close (No related issue: …), so no acceptance criteria apply. I found no embedded instructions in the PR context.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: efefa636fd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/pr-review-cli.yml Resolved
Grouping by post-image blob alone let different changes converge: two
files edited to the same content from different pre-images, or a control
file and a harmless file both truncated to empty, grouped together and
the copy's change was never read while coverage reported it complete.

The full-diff step now keys each group on the whole raw-diff record
(both modes, both blobs, status), and only modifications of regular
files (100644/100755 on both sides) group: additions, deletions,
renames, copies, type changes, symlinks and submodules never do. A path
matching a hold-paths glob, matched as the scope step matches
docs-only-paths, never joins a group in any position, so the TRUSTED
PATHS instruction is never undercut. The same change lands in all three
legs, and the prompt now says copies are files whose whole change is
identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 24e7d8b Nov 1, 2026
29 checks passed
@kyle-sexton
kyle-sexton deleted the feat/review-dedupe-identical-files branch November 1, 2026 16:56
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.

1 participant