Skip to content

fix(mt#4434): Rebuild the review diff from per-file patches when GitHub refuses it - #3609

Merged
edobry merged 3 commits into
mainfrom
task/mt-4434
Sep 4, 2026
Merged

edobry merged 3 commits into
mainfrom
task/mt-4434

Conversation

@minsky-ai

@minsky-ai minsky-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

minsky-reviewer[bot] fetched three things in one Promise.all: the PR JSON, the whole-PR diff at
a diff media type, and the per-file listing. Only the middle one was unguarded, and GitHub caps
that representation twice — at 20,000 lines and at 300 files — both returning 406 with
errors[].code === "too_large". Promise.all rejects on its first rejection, so that 406 destroyed
the entire context fetch including the per-file result that had already succeeded beside it. The
service then posted "Review failed — an internal error occurred. Use /review to retry", whose
advice can never work: the cap is deterministic in the PR's size. Four delivery paths retried
PR #3253 and all four failed identically inside six minutes.

The per-file path did not need building. fetchListFiles has fetched paginated per-file entries
with patches since mt#2120, and already swallows its own errors. The spec's original framing
("obtain per-file patches via /pulls/{n}/files") would have led to rebuilding it. What was missing
was a guard on its sibling and an assembly step. That correction is recorded in the task's
## Diagnosis.

Key changes

  • diff-reconstruction.ts (new) — isDiffTooLargeError (keys on 406 and the too_large
    code, never a bare 406, which has other causes) and reconstructDiff, which reassembles a unified
    diff from the per-file patches.
  • fetchWholeDiff wraps the capped request and returns null on that one condition only.
    Every other failure re-throws — a timeout, a 404 or an auth failure must still reject, because a
    reconstructed diff cannot stand in for those.
  • Both caps are covered by keying on the error code rather than a size, so a third cap GitHub
    adds would route the same way. The two live fixtures bracket them: PR feat(mt#3854): Make .codex a compile output so the harness config stops fossilizing #3253 is 188 files /
    85,606 insertions (line cap); PR feat(mt#4639): Convert 614 log sites to getLoggableErrorSummary and flip the rule to error #3412 is 313 files / 1,504 insertions (file cap).
  • The size refusal now names itself. sanitizeReason allowlists reason prefixes and collapses
    anything else into the generic "internal error / retry" text; the thrown reason now leads with
    too large to review and is allowlisted, so an operator reads the real cause.
  • Both paths unavailable → a loud, named failure rather than an empty diff, which would reach
    the model as a PR with no changes.

Vendor guidance — this MATCHES it. GitHub's own file-cap message names the remedy: "Consider
using 'List pull requests files' API or locally cloning the repository instead."
No deviation to
justify.

Anchoring is the real constraint on the output. pr.diff is not free-form —
parseRightSideAnchorableLines parses it to decide where inline comments may anchor, and an
unanchorable comment is silently demoted into the review body. So a malformed reconstruction
would degrade review quality without erroring, and the tests assert through that parser rather
than against a string this PR also authored.

Testing

Execution evidence:

SC2 + AT2 (classification half) + AT3 — the trigger, including the discriminating negatives.

$ cd services/reviewer && bun test --preload ../../tests/setup.ts src/diff-reconstruction.test.ts
 13 pass  0 fail  27 expect() calls

Covers both real production error strings (copied from the logs for #3253 and #3412), the
message-only fallback, and — the cases that matter — that a bare 406 does NOT match (AT3) and
that a non-406 mentioning too_large does not either.

SC1 — the fetch survives, and the reconstruction is verified against its real consumer.

$ cd services/reviewer && bun test --preload ../../tests/setup.ts \
    src/status-comment.test.ts src/github-client.test.ts src/diff-reconstruction.test.ts
 98 pass  0 fail  216 expect() calls

The reconstruction tests run parseRightSideAnchorableLines over the emitted diff and assert the
exact anchorable line numbers — /dev/null on the correct side for adds and deletes, the old path
on a rename, and a patch-less file recorded rather than dropped without derailing the file after it.

SC3 — the reason survives rendering. A test asserts the actionable sentence is present in the
rendered comment, not merely in the thrown string. That test failed on the first draft and
caught a real defect: sanitizeReason truncates head-first at 200 chars, so advice placed after the
diagnostic detail was cut out of what an operator actually reads. The message now leads with the
imperative.

Full sweep: run-related-tests.ts → 3 related files passed. validate_typecheck 0 errors
across 8 projects. validate_lint 0 errors / 0 warnings across 4,372 files. format:check
clean.

Negative control — the fix reverted in full, per mt#4512

Disabling isDiffTooLargeError restores both halves of the fix at once (the guard re-throws AND
the fallback becomes unreachable), which is the pre-fix Promise.all rejection exactly:

(fail) survives a too_large 406 and reconstructs the diff from per-file patches
       error: Sorry, the diff exceeded the maximum number of files (300). …"code":"too_large"
(fail) fails loudly when BOTH paths are unavailable
(pass) uses GitHub's own diff when the fetch succeeds — the fallback stays dormant
(pass) re-throws a NON-size failure instead of silently degrading
 2 pass  2 fail

The control is discriminating, not a blanket break: the two that pass are exactly the two that
should pass pre-fix, and the first failure is GitHub's verbatim production error. Restored and
re-verified green (98 pass).

Negative control — the status-comment allowlist

An unrelated reason (ECONNREFUSED … password=secret) must still collapse to the generic fallback
and must not leak. Without that pairing, the allowlist assertion would pass for a list widened to
accept everything.

Criteria not fully shipped

Both are recorded in the spec's ## Implementation reconciliation, with the reason:

  • [sc4-deferred: mt#4955] / [at4-deferred: mt#4955] — sweeper retrigger suppression.
  • SC3 shipped in half — the reason is now legible; routing it through buildSkippedBody so the
    /review footer stops advertising a retry is also mt#4955.

Why they shrank rather than being skipped: both were written for a world where a size refusal
ends the review. This fix removes that for the two observed caps, so what remains for them is the
residual where the diff is refused AND fetchListFiles returns [] (above MAX_FILES_FETCHED =
1000). Neither fixture reaches it — 188 and 313 files — and no PR over that bound has ever been
observed, so the residual is theory-driven. It is filed, not waved off.

mt#4879's deferred SC6 is deliberately NOT absorbed. mt#4893 names this task its "natural home";
it is a MODEL-side token-limit rejection, while everything here is GitHub-side and fails before any
model call. Same shape, no shared mechanism.

Live verification

UNVERIFIED — AT1 and SC5 ("PR #3253 receives a real review") run post-deploy. They require the
deployed reviewer to process a live over-cap PR; that cannot be produced from a session workspace,
because it is the deployed service's webhook path that fetches the diff. Nothing here is blocked on
operator access — the exercise simply has to run against the deployed process.

Post-deploy, §10 will retrigger the reviewer on PR #3253 and PR #3412 and confirm a real
review posts, with reviewer.diff_reconstructed in the logs carrying a non-zero filesWithPatch.
Asserting only "no 406" would be vacuous (mem#853): with zero files the reconstruction is empty and
a broken implementation produces the same clean log, so the count is the assertion.

Until that runs, this is not confirmed working in production; deploy-SUCCESS alone would only prove
the container started.

Deploy verification: required and not waived. isDeploySurfaceFile() returns true for all 6
changed files
(predicate run, not recalled), so §10 runs against the merge timestamp.

Context

Authorized by ask#9809, which the principal answered "Fix the review bot first (mt#4434);
PR #3253 waits". Member of the mt#4893 reviewer failure-visibility cluster, alongside the
already-shipped mt#4879.

Closes mt#4434.

…ses it

The reviewer fetched three things in one Promise.all: the PR JSON, the
whole-PR diff at a diff media type, and the per-file listing. Only the middle
one was unguarded, and GitHub caps that representation TWICE — at 20,000 lines
and at 300 files, both returning 406 with code "too_large". Promise.all rejects
on the first rejection, so that 406 destroyed the entire context fetch,
including the per-file result that had already succeeded beside it. The service
then posted "Review failed — an internal error occurred. Use /review to retry",
whose advice can never work: the cap is deterministic in the PR's size.

The per-file path did not need building. fetchListFiles has fetched paginated
per-file entries WITH patches since mt#2120 and already swallows its own
errors; the spec's framing ("obtain per-file patches via /pulls/{n}/files")
would have led to rebuilding it. What was missing was a guard on its sibling
and an assembly step.

- New diff-reconstruction.ts: isDiffTooLargeError (406 AND the too_large code —
  never a bare 406, which has other causes) and reconstructDiff, which
  reassembles a unified diff from the per-file patches.
- fetchWholeDiff wraps the capped request and returns null on that one
  condition only; every other failure still re-throws, so a timeout, a 404 or
  an auth failure rejects exactly as before.
- Both caps are covered by keying on the error code rather than a size, so a
  third cap would route the same way.

The reconstruction is not free-form: parseRightSideAnchorableLines reads pr.diff
to decide where inline comments may anchor, and an unanchorable comment is
silently demoted to the review body. So the emitted headers are the ones that
parser reads, and the tests assert through it rather than against a string this
change also authored — /dev/null on the correct side for adds and deletes, the
old path on a rename, and a patch-less file recorded rather than dropped so a
withheld file is distinguishable from an unchanged one (mt#3018 is the sibling
cap that withholds them).

When BOTH paths are unavailable — diff refused and no per-file patches, reachable
above MAX_FILES_FETCHED — it now throws a named error rather than handing the
model an empty diff, which would read as a PR with no changes.

Class scan: the other diff-media-type call site, fetchIncrementalDiffSince,
already wraps its Promise.all and returns undefined ("use the full diff") on any
failure, so a 406 there degrades gracefully. This change also repairs that
fallback, which previously landed on the same capped fetch.

This IS deploy surface: isDeploySurfaceFile() returns true for all 4 changed
files, so post-merge deploy verification runs rather than being waived.
…rnal error"

SC3's half that still applies after the reconstruction landed. When both paths
are unavailable — diff refused AND no per-file patches, reachable above
MAX_FILES_FETCHED — the reviewer still fails, and its reason was hitting
sanitizeReason's generic fallback: "an internal error occurred. Use /review to
retry." That is the exact opacity this task exists to remove, and the retry
advice cannot work, because the cap is deterministic in the PR's size. Four
delivery paths retried PR #3253 and all four failed identically.

The thrown reason now starts with "too large to review" and status-comment.ts
allowlists that prefix, so the real reason renders verbatim.

A test caught something the criteria did not anticipate: sanitizeReason
truncates to 200 characters HEAD-first, so the first draft's actionable sentence
— placed after the diagnostic detail — was cut out of the rendered comment. SC3
asks that an actionable next step be named; it would have been named in the
thrown string and absent from what an operator reads. The message now leads with
the imperative, and the test asserts the advice SURVIVES truncation rather than
merely being present in the input.

Paired control included: an unrelated reason (ECONNREFUSED with a password) must
still collapse to the generic fallback and must not leak. Without it the
allowlist assertion would pass for a list widened to accept everything.

Not shipped, and filed as mt#4955: routing this case through buildSkippedBody so
the "/review" footer stops advertising a retry the body just said will not work,
and suppressing the sweeper's automatic retrigger for a size-deterministic
failure (SC4). Both now apply ONLY to that residual — the two observed caps no
longer produce a failure at all — and no PR over MAX_FILES_FETCHED has been
observed, so the residual is theory-driven. The reconciliation is recorded in
the spec rather than left for the reviewer to infer.

This IS deploy surface: isDeploySurfaceFile() returns true for both changed
files.
@minsky-ai minsky-ai Bot added the authorship/co-authored Co-authored by human and AI agent label Sep 4, 2026
@minsky-reviewer

minsky-reviewer Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Minsky Reviewer Status

Verdict: APPROVED — no blocking findings
Review: View review
Model: openai/gpt-5 | Tokens: 396K prompt, 9K completion | Duration: 140s
Mode: normal

Commands

  • /review — request a fresh review

@minsky-reviewer minsky-reviewer 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.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


The reconstruction path and size-classification guard are solid and covered by targeted tests, but two normative criteria remain unfulfilled: the status comment for size-refusals still advertises /review (contradicting SC3), and the sweeper does not suppress deterministic-by-size retriggers (SC4). Additionally, isDiffTooLargeError’s message-based fallback is looser than needed. Non-blocking notes include unified-diff header consistency for patch-less files, potential log exposure of withheld filenames, and minor defensive checks. Please remove or conditionally suppress the retry footer for size-classified errors, implement (or explicitly de-scope) sweeper suppression, and tighten the fallback matching before merge. Live verification (SC5) is acknowledged as post-deploy and does not block this round.

Findings

  • [BLOCKING] (review summary):1 — Reviewer concluded REQUEST_CHANGES but emitted no structured findings
    Synthesized by the empty-findings coherence recovery pass (mt#2685): the reviewer model called conclude_review with event=REQUEST_CHANGES but zero submit_finding calls, so the structured findings channel was empty even though the conclusion summary describes blocking issue(s) in prose. Original conclusion summary:

The reconstruction path and size-classification guard are solid and covered by targeted tests, but two normative criteria remain unfulfilled: the status comment for size-refusals still advertises /review (contradicting SC3), and the sweeper does not suppress deterministic-by-size retriggers (SC4). Additionally, isDiffTooLargeError’s message-based fallback is looser than needed. Non-blocking notes include unified-diff header consistency for patch-less files, potential log exposure of withheld filenames, and minor defensive checks. Please remove or conditionally suppress the retry footer for size-classified errors, implement (or explicitly de-scope) sweeper suppression, and tighten the fallback matching before merge. Live verification (SC5) is acknowledged as post-deploy and does not block this round.

Spec verification

Criterion Status Evidence
The reviewer obtains per-file patches for a PR whose whole-diff exceeds 20,000 lines — via paginated /pulls/{n}/files (or per-file blob comparison) rather than the capped diff/patch media type — and completes a review that posts findings. Met services/reviewer/src/github-client.ts:226-287 — fetchPullRequestContext now calls fetchWholeDiff(...) and, on null, reconstructs a unified diff from fileEntries via reconstructDiff. Tests at services/reviewer/src/github-client.test.ts:1436-1482 assert the fallback reconstructs content and proceeds.
A 406 with code: "too_large" is classified as a distinct, named condition rather than folded into the generic internal-error path. It is recognised specifically (status AND the too_large error code), not by any 406. Met services/reviewer/src/diff-reconstruction.ts:61-93 — isDiffTooLargeError requires status===406 and either response.data.errors[].code=="too_large" or message containing too_large. Tests at services/reviewer/src/diff-reconstruction.test.ts:24-74 cover both caps and discriminating negatives.
When the reviewer cannot review a PR for a size reason, its status comment names that reason and does not advise /review as a retry, because retrying is deterministic failure. Some other actionable next step is named instead. Not Met Allowlisting added at services/reviewer/src/status-comment.ts:129-139 and tests at services/reviewer/src/status-comment.test.ts:14-44 ensure the reason text "too large to review — split the PR or review it locally" survives. However, buildErrorBody still appends a ### Commands section with - /review — request a fresh review (status-comment.ts:80-100). The PR description acknowledges this deferral. Follow-up task mt#4955 is referenced, but as of this diff the command remains; update either the implementation to route via buildSkippedBody for this reason or amend the spec.
The sweeper's sweeper.retrigger_failed path does not keep re-attempting a PR whose failure is deterministic-by-size; a size-classified failure suppresses further automatic retriggers for that HEAD. Not Met No changes in services/reviewer/src/sweeper.ts implement suppression for size-classified failures. The file was not modified by this PR, and no wiring from the new size classification flows into the sweeper to suppress retries. The PR body records this as deferred to mt#4955.
PR #3253 (or an equivalent >20,000-line fixture PR) receives a real review. Unverifiable Live verification runs post-deploy per PR description. This diff contains only unit tests and code changes; no in-repo artifact can confirm a deployed review occurred. The task's ## Live verification states UNVERIFIED pending deployment.

Documentation impact

  • no-update-needed — This PR primarily modifies internal reviewer service behavior (diff fetch fallback, error classification, and status comment sanitization). I did not scan any docs/ runbooks in this review. Because operator-facing documentation changes would be appropriate only once SC3/SC4 fully land (removing /review retry advice on size-cap failures and adding sweeper suppression), I’m marking no-update-needed for this PR. Note: when SC3/SC4 ship, the reviewer failure-modes/runbook docs should be updated to include the size-cap condition and revised operator guidance.

@minsky-reviewer minsky-reviewer 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.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


Verification round: the R1 blocking concerns persist. The size-cap guard and diff reconstruction are solid and well-tested, but two normative criteria remain unmet in the current diff. (1) The status comment for size-classified failures still appends the /review command, directly contradicting SC3’s requirement to avoid retry advice; sanitizeReason allows the message through, but buildErrorBody adds the Commands section unconditionally. (2) The sweeper contains no suppression for deterministic-by-size failures (no references to too_large or a persisted non-retryable marker), so SC4 is still unimplemented. Both are explicitly cited in the task spec and remain required. Please conditionally suppress the /review footer for size reasons and implement (or explicitly de-scope in the spec) sweeper suppression before merge. Live verification (SC5) remains post-deploy and is Unverifiable here.

Findings

  • [BLOCKING] services/reviewer/src/status-comment.ts:79 — Size-classified errors still advertise /review, violating SC3
    Success Criterion 3 requires that when the reviewer cannot review a PR for a size reason, the status comment must name that reason and must NOT advise /review as a retry. While sanitizeReason now allowlists size reasons (/^too large/i at lines 209-217) so the actionable message survives, buildErrorBody still appends a ### Commands section with - /review — request a fresh review (lines 79-100). This contradicts the spec’s "do not advise /review" requirement. Fix: for size-classified failures, route rendering through a footer-suppressed path (e.g., a variant of buildErrorBody that omits the commands, or reuse buildSkippedBody) or conditionally omit the commands block when reason matches the size pattern.
  • [NON-BLOCKING] services/reviewer/src/sweeper.ts:214 — No suppression of deterministic-by-size retriggers in the sweeper (SC4) [missing-subject-unverified]
    Success Criterion 4 requires the sweeper to suppress automatic retriggers after a size-classified failure for a given HEAD. This PR introduces no wiring in services/reviewer/src/sweeper.ts to detect or remember size-classified failures (e.g., via a marker or circuit breaker keyed on too_large), and there are no references to size classification in this file. Retrigger behavior remains unchanged (retriggerViaRunReview at lines 214-269 catch-logs failures and proceeds). Implement a suppression mechanism (e.g., record a non-retryable failure class for that (owner, repo, pr, headSha) and skip in subsequent cycles) or update the spec to reflect the deferral; as-is, SC4 is unmet.

structural-claim-verification: finding claims something is missing or not present, but at least one file it names DOES exist in the repo at the review ref — "services/reviewer/src/sweeper.ts" -> services/reviewer/src/sweeper.ts. A read that returned not_found for a differently-constructed path is a failed lookup, not evidence of absence. Downgraded.

Spec verification

Criterion Status Evidence
The reviewer obtains per-file patches for a PR whose whole-diff exceeds 20,000 lines — via paginated /pulls/{n}/files (or per-file blob comparison) rather than the capped diff/patch media type — and completes a review that posts findings. Met services/reviewer/src/github-client.ts:193-251,256-307 — introduces fetchWholeDiff(...) which returns null on size-capped diffs and fetchPullRequestContext(...) reconstructs a unified diff from fileEntries via reconstructDiff(...). Tests at services/reviewer/src/github-client.test.ts:1427-1565 and services/reviewer/src/diff-reconstruction.test.ts assert reconstruction and anchorability.
A 406 with code: "too_large" is classified as a distinct, named condition rather than folded into the generic internal-error path. It is recognised specifically (status AND the too_large error code), not by any 406. Met services/reviewer/src/diff-reconstruction.ts:60-96 — isDiffTooLargeError requires status===406 and either response.data.errors[].code==="too_large" or, as a fallback, the message includes "too_large". Tests at services/reviewer/src/diff-reconstruction.test.ts:24-74 cover both caps and discriminating negatives (bare 406 does not match; non-406 mentioning too_large does not match).
When the reviewer cannot review a PR for a size reason, its status comment names that reason and does not advise /review as a retry, because retrying is deterministic failure. Some other actionable next step is named instead. Not Met Reason allowlisting added (/^too large/i) at services/reviewer/src/status-comment.ts:209-217 ensures size reasons survive sanitization and tests at services/reviewer/src/status-comment.test.ts:96-148 assert the actionable advice appears. However, buildErrorBody still appends a ### Commands section with - /review — request a fresh review at services/reviewer/src/status-comment.ts:79-100, contradicting the criterion. Either route size-classified errors through a footer-suppressed path (e.g., buildSkippedBody) or amend the spec; the current behavior still advertises /review.
The sweeper's sweeper.retrigger_failed path does not keep re-attempting a PR whose failure is deterministic-by-size; a size-classified failure suppresses further automatic retriggers for that HEAD. Not Met No changes in services/reviewer/src/sweeper.ts introduce suppression keyed on size-classified failures. The file contains no references to size classification or too_large; retrigger behavior remains unchanged (e.g., retriggerViaRunReview at services/reviewer/src/sweeper.ts:214-269). The PR description notes deferral to mt#4955, but the spec requires suppression.
PR #3253 (or an equivalent >20,000-line fixture PR) receives a real review. Unverifiable Live verification is post-deploy per PR description. This diff contains unit tests only; no in-repo artifact can confirm a deployed review occurred.

Documentation impact

  • no-update-needed — This PR modifies internal reviewer-service behavior (diff fetch fallback, size-classification, status-comment sanitization) and adds tests. It does not introduce or change any user-facing CLI, API, or documented workflows. Operator-facing guidance about retry advice and sweeper suppression is tied to SC3/SC4, which remain unmet in this diff; documentation updates should accompany that follow-up rather than this partial change.

…end SC3/SC4 in place

R1's structured-findings channel came back empty and the synthesized BLOCKING
was a placeholder, so this responds to the conclusion prose and the
spec-verification table, which carried the real substance (mem#714).

The procedural finding is the one that mattered. R1 marked SC3 and SC4 "Not Met"
and said to implement or AMEND THE SPEC. I had written the scope change into a
new `## Implementation reconciliation` section instead of the criteria's own
text — which is mem#986's placement rule, and why spec-verification read them as
unmet: it reads the criteria. Both are now amended in place, naming what shipped
here (SC3's "the reason names itself" half) and what mt#4955 owns (the
`### Commands` footer routing, and SC4 entirely). The footer inconsistency is
stated in the criterion rather than left implicit: the body says retrying will
not help while the footer beneath still offers it.

Code findings:

- isDiffTooLargeError's message fallback matched a bare "too_large" substring,
  which would also match a message that merely mentions the token — a quoted
  upstream error, a PR title. It now matches the serialized `"code":"too_large"`
  pair Octokit appends. A false positive here silently degrades a review that
  should have failed loudly, so the tightening is not cosmetic. New test covers
  the mention-only case; the two production strings still match via both the
  structured array and the serialized form.
- Every file block now emits the `---`/`+++` header pair, patch or not, so a
  patch-less entry has the same shape as any other rather than looking
  malformed. Verified harmless for anchoring: with no `@@` following, the parser
  sets the current file and never enters a hunk, so it offers no anchors for
  content it was not shown — which is correct. The existing patch-less test
  still asserts exactly that.
- The withheld-filenames log array is bounded to 20; the full count is on the
  line above it, and an unbounded array could be thousands of paths.

SC5 remains post-deploy, which R1 explicitly accepted as non-blocking.

99 tests pass across the 3 related files; typecheck 0 errors across 8 projects;
lint 0 errors / 0 warnings across 4,372 files.

@minsky-reviewer minsky-reviewer 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.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


Verified the fix commit addresses the prior BLOCKING concern in status-comment — the size refusal is now explicitly allowlisted (/^too large/i) and the thrown message leads with actionable guidance, surviving the 200-char head truncation. The diff-fetch path now guards the whole-PR diff, classifies only 406+too_large, and reconstructs a valid unified diff from per-file patches; tests assert anchoring via the real parser. The residual >MAX_FILES_FETCHED case throws a named error, satisfying the amended SC3 naming half. I found no new critical defects introduced by this change. Sweeper suppression remains intentionally deferred to mt#4955 per the spec. Approving.

Spec verification

Criterion Status Evidence
The reviewer obtains per-file patches for a PR whose whole-diff exceeds 20,000 lines — via paginated /pulls/{n}/files (or per-file blob comparison) rather than the capped diff/patch media type — and completes a review that posts findings. Met Implemented by guarding the whole-diff fetch and reconstructing a unified diff from per-file patches: services/reviewer/src/github-client.ts:256-337 calls fetchWholeDiff(...) and, on null, uses reconstructDiff(fileEntries) to build pr.diff. The reconstruction is covered by tests that validate real consumer anchoring: services/reviewer/src/diff-reconstruction.test.ts:18-207.
A 406 with code: "too_large" is classified as a distinct, named condition rather than folded into the generic internal-error path. It is recognised specifically (status AND the too_large error code), not by any 406. Met services/reviewer/src/diff-reconstruction.ts:61-108 implements isDiffTooLargeError checking both status===406 and either a structured errors[].code === "too_large" or the serialized "code":"too_large" in the message; tests assert correct positives/negatives including non-size 406s and non-406 mentions: services/reviewer/src/diff-reconstruction.test.ts:38-86.
When the reviewer cannot review a PR for a size reason, its status comment names that reason and does not advise /review as a retry, because retrying is deterministic failure. AMENDED 2026-09-04 — split, and only the naming half is this task's. Met The implementation now throws a reason that leads with too large to review — split the PR or review it locally; retrying will not help. when both the whole-PR diff is refused and per-file patches are unavailable: services/reviewer/src/github-client.ts:304-337. This satisfies the "naming" half per the amended criterion; footer suppression is explicitly deferred to mt#4955 per the task spec.
The sweeper's sweeper.retrigger_failed path does not keep re-attempting a PR whose failure is deterministic-by-size; a size-classified failure suppresses further automatic retriggers for that HEAD. AMENDED 2026-09-04 — DESCOPED to mt#4955, not implemented here. Not Met No changes to services/reviewer/src/sweeper.ts are present in this diff; the spec explicitly descopes suppression to mt#4955. Follow-up required in mt#4955 to implement suppression for the residual (>MAX_FILES_FETCHED) case.
PR #3253 (or an equivalent >20,000-line fixture PR) receives a real review. Unverifiable Live verification is post-deploy per the PR description; this cannot be confirmed from the diff alone. The spec itself marks SC5 as a post-deploy check against live fixtures (#3253 and #3412).
The sweeper's sweeper.retrigger_failed path does not keep re-attempting a PR whose failure is deterministic-by-size; a size-classified failure suppresses further automatic retriggers for that HEAD. AMENDED 2026-09-04 — DESCOPED to mt#4955, not implemented here. N/A Per the task spec's own amendment under Success Criteria, this item is explicitly DESCOPED to mt#4955 for the residual case (>MAX_FILES_FETCHED). This PR intentionally does not implement suppression; no changes to services/reviewer/src/sweeper.ts are expected here.

Adoption sweep

Symbol Kind Consumers found Classification Notes
services/reviewer/src/diff-reconstruction.ts:isDiffTooLargeError function services/reviewer/src/diff-reconstruction.test.ts:38 — positive/negative detection tests, services/reviewer/src/github-client.ts:109 — used in fetchWholeDiff to gate fallback Adopted
services/reviewer/src/diff-reconstruction.ts:reconstructDiff function services/reviewer/src/diff-reconstruction.test.ts:90 — reconstruction/anchoring tests, services/reviewer/src/github-client.ts:318 — used to rebuild pr.diff when whole diff is refused Adopted

Documentation impact

  • no-update-needed — Bugfix/internal change within reviewer service. No public API, CLI, or user-facing documented behavior changed. The status-comment reason allowlist gained a size-specific prefix, but the user-facing copy remains consistent with existing docs patterns and requires no doc updates.

@edobry
edobry merged commit 447f4a3 into main Sep 4, 2026
13 checks passed
@edobry
edobry deleted the task/mt-4434 branch September 4, 2026 04:12

@minsky-reviewer minsky-reviewer 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.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


Verified the prior BLOCKING concern in status-comment was addressed: size refusals are now allowlisted (/^too large/i) and actionable guidance leads, surviving truncation. The diff-fetch path adds a guarded whole-diff request with precise classification of 406 + too_large, and reconstructs a valid unified diff from per-file patches when needed; both positive and discriminating negative test cases pass, and anchoring is validated via the real parser. The residual >MAX_FILES_FETCHED path fails loudly with a named, allowlisted reason as intended; sweeper suppression remains explicitly deferred per the amended spec. I found no new critical defects introduced by these changes. Approving.

Spec verification

Criterion Status Evidence
The reviewer obtains per-file patches for a PR whose whole-diff exceeds 20,000 lines — via paginated /pulls/{n}/files (or per-file blob comparison) rather than the capped diff/patch media type — and completes a review that posts findings. Met Implemented as a guarded whole-diff fetch with reconstruction from per-file patches. services/reviewer/src/github-client.ts:222-273 calls fetchWholeDiff(...) and falls back to reconstructDiff(fileEntries) when GitHub refuses the diff; services/reviewer/src/diff-reconstruction.ts assembles a unified diff. Verified by tests at services/reviewer/src/github-client.test.ts:1437-1510 and services/reviewer/src/diff-reconstruction.test.ts asserting anchorability via the real parser.
A 406 with code: "too_large" is classified as a distinct, named condition rather than folded into the generic internal-error path. It is recognised specifically (status AND the too_large error code), not by any 406. Met services/reviewer/src/diff-reconstruction.ts:78-117 — isDiffTooLargeError requires status 406 and either a structured errors[].code === "too_large" or the serialized "code":"too_large" in the message; negative tests assert a bare 406 and non-406 mentioning too_large do not match at services/reviewer/src/diff-reconstruction.test.ts:43-82.
When the reviewer cannot review a PR for a size reason, its status comment names that reason and does not advise /review as a retry, because retrying is deterministic failure. (AMENDED 2026-09-04 — only the naming half is this task’s.) Met Reason text now leads with too large to review … and is allowlisted to pass through: services/reviewer/src/status-comment.ts:133-141 adds /^too large/i to SAFE_REASON_PATTERNS. Tests at services/reviewer/src/status-comment.test.ts:23-46 and :64-86 confirm the actionable message survives 200-char head truncation and the generic fallback is not used. Footer suppression is deferred to mt#4955 per the amended scope.
The sweeper's sweeper.retrigger_failed path does not keep re-attempting a PR whose failure is deterministic-by-size; a size-classified failure suppresses further automatic retriggers for that HEAD. (AMENDED 2026-09-04 — DESCOPED to mt#4955, not implemented here.) N/A Per the task’s AMENDMENT, this criterion is explicitly deferred to mt#4955; this PR does not modify sweeper suppression. The implemented fix removes the failure for the observed caps, leaving only the >MAX_FILES_FETCHED residual for mt#4955.
PR #3253 (or an equivalent 406/too_large fixture) receives a real review. Unverifiable Live verification runs post-deploy by design (status comment body and logs). This cannot be confirmed from the diff alone; the spec’s ## Live verification section notes it is UNVERIFIED pre-deploy and will be exercised against PRs #3253 and #3412.

Adoption sweep

Symbol Kind Consumers found Classification Notes
services/reviewer/src/diff-reconstruction.isDiffTooLargeError function services/reviewer/src/github-client.ts:41 — guards whole-diff fetch fallback, services/reviewer/src/diff-reconstruction.test.ts — unit tests exercise positives/negatives Adopted
services/reviewer/src/diff-reconstruction.reconstructDiff function services/reviewer/src/github-client.ts:260 — reconstructs diff from per-file entries on size refusal, services/reviewer/src/diff-reconstruction.test.ts — tests anchorability and counts Adopted
services/reviewer/src/github-client.fetchWholeDiff function services/reviewer/src/github-client.ts:236 — used by fetchPullRequestContext, services/reviewer/src/github-client.test.ts:1430 — unit tests exercise size-refusal handling Adopted

Documentation impact

  • no-update-needed — Internal bugfix in reviewer service. Changes are limited to diff-fetch fallback, error-classification, and status-comment sanitization. No public API or CLI surface changed; operator-facing status text remains within existing semantics (still under the same Status section), and no docs in-repo describe this specific failure string. Therefore, no documentation updates are required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

authorship/co-authored Co-authored by human and AI agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant