Why this research?
An AI proposes a software fix. Before accepting it, you ask another AI to review the change. What evidence should that reviewer receive, and would another model make the same decision?
We wanted to understand whether test results help AI reviewers make better acceptance decisions, whether a concise summary is enough, and whether calling the same evidence “independent” changes its influence.
A reviewer that rejects everything can avoid faulty approvals while blocking useful work. We therefore examined both sides: acceptance of patches that passed the benchmark and approval of patches that failed it. This is a study of how reviewers interpret available evidence, not whether they can discover defects independently.
How we tested AI reviewers
We used 60 public software tasks, each with one AI-generated patch: a proposed code change intended to fix the reported problem. Three reviewers received the same issue and patch under five evidence scenarios: GPT-5.6 Terra, Claude Sonnet 5 and Gemini 3.8 Flash.
- No report: issue and proposed fix only.
- Factual summary: a short account of recorded test results.
- Full report: the complete log, with no source label.
- Patch-producing source: the full log attributed to the system that proposed the fix.
- Separate evaluator: the identical log attributed to an evaluator that did not propose it.
The summary retained failures and warnings. The last three scenarios used identical report bodies; their source labels were explicitly hypothetical. Evidence and selection details.
Each reviewer was asked twice in fresh sessions, without tools, to approve, hold or abstain. Hold meant requesting more work; it did not necessarily mean the patch was wrong. The 20-task initial batch and 40-task follow-up produced 1,692 usable reviews. Repeats are not independent tasks. Settings and missing responses.
The reports came from the same benchmark evaluation used to label patches as passing or failing. “Passing” therefore means passing those tests; it does not mean independently proven correctness.
Three findings at a glance
Test evidence reduced faulty approvals
Both the factual summary and full report reduced observed faulty approvals to zero on matched benchmark-failing tasks. The full report showed no further reduction, but that does not establish that the formats are equivalent.
Reviewers differed in accepting passing patches
With the same full report, Claude accepted more benchmark-passing fixes than Terra or Gemini. None approved a benchmark-failing fix on that matched task set.
The source label had no consistent follow-up benefit
Changing the identical report's stated source to a separate evaluator did not consistently increase approval in the larger follow-up. The initial positive pattern weakened.
1. Test evidence reduced faulty approvals
Did a summary help? Did a full report help more?
Without a test report, reviewers sometimes approved patches that failed the benchmark. With either a faithful summary or the full report, those approvals disappeared on the matched failing tasks.
Faulty approvals fell with test evidence
The same 15 benchmark-failing tasks · 30 reviews per setting
Claude Sonnet 5
GPT-5.6 Terra
Gemini 3.8 Flash
The full report showed no further reduction on this measure. That makes a concise summary worth testing in a review workflow, but it does not establish that the two formats are interchangeable: they may differ in accepting passing patches or on new tasks.
Practical takeaway: give the reviewer concrete test evidence, including failures and warnings. Test whether a faithful summary supports the decisions you need before assuming a longer log is better. Worked example · Missing-response check.
2. The same evidence led to different acceptance decisions
Did reviewers agree on which passing patches to accept?
No. Given the same full report, Claude accepted more benchmark-passing fixes than Terra or Gemini. None approved a benchmark-failing patch on this matched task set.
Passing fixes were not accepted equally
Same full report · 18 passing tasks · 36 reviews per model
On the same matched set’s 17 failing tasks, every reviewer approved 0 of 34 reviews.
A withheld approval is not automatically an error. In one passing example, Gemini acknowledged the fix but requested a committed regression test and removal of a reproduction script. We did not measure whether those requests were necessary or what following them would cost. The example and its selection rule.
Practical takeaway: evaluate what your reviewer leaves waiting as well as what it accepts. A low faulty-approval rate alone does not tell you how much useful work it blocks.
These are configured reviewers, not a universal ranking. Sonnet used a native structured-output constraint in the follow-up; the configuration details matter when interpreting the comparison.
3. An “independent” label had no consistent follow-up benefit
Did changing the stated source change approval?
The early pattern did not hold consistently. Calling an identical report the work of a separate evaluator initially increased approval on average. In the larger follow-up, the average was near zero and the individual reviewers moved in different directions.
The early source-label pattern weakened
Approval change from “patch producer” to “separate evaluator.” Positive means more approvals, but not necessarily better decisions.
Claude Sonnet 5
GPT-5.6 Terra
Gemini 3.8 Flash
Three-model mean
Initial batch: 20 tasks; follow-up: 40 different tasks. Each comparison uses complete paired tasks. The mean requires all three models on the same tasks.
The two phases used different tasks, and Sonnet's response constraint changed before the follow-up. We keep the phases separate rather than interpreting their difference as the effect of one change. Paired comparisons and uncertainty.
Related research shows that surface cues can bias code judges ↗ and authorship labels can change judgments of unchanged material ↗. Our finding is narrower: the claimed source of a test report did not consistently increase approval in this follow-up.
Practical takeaway: don't rely on the word “independent” to improve review decisions. This experiment changed a label, not the tests; it does not tell us whether genuinely independent testing would help.
How to use these findings
For an AI-assisted review workflow:
- Give reviewers actual test outcomes, preserving failures, warnings and relevant context.
- Compare concise summaries with full reports on the same work. Measure both faulty approvals and acceptance of passing fixes.
- Inspect withheld approvals: is the requested follow-up useful, or is it unnecessary friction? Our study identifies that question; it did not measure the answer.
- Test the reviewer configuration on your own tasks before using it as an acceptance gate.
Keep the test harness and human judgment in the workflow. A second model is another source of evidence, not a substitute for verifying a fix.
What this study does and doesn’t tell us
Use this study to design reviewer evaluations and decide what evidence to test in your workflow. It does not establish a universal model ranking or prove that an AI reviewer can certify software correctness. We selected public tasks, some reviews were missing, and model training exposure is unknown. The supplied reports came from the same evaluation used to label the patches. The benchmark comparisons were added after collection, and Sonnet's formatting constraint changed between phases.
Tests can miss defects ↗ or reject acceptable implementations ↗. Validate these patterns on your own work; do not treat zero observed faulty approvals as zero risk. Full limitations.
The bottom line
Test evidence helped these reviewers avoid faulty approvals, but it did not make their acceptance decisions interchangeable. Evaluate both the bad fixes a reviewer accepts and the good work it leaves waiting. Judge evidence by its contents, not just its stated source.
Methods and supporting material
Appendix
Supporting evidence for the article: how the comparisons were made, the complete results, and the actual inputs behind the examples. Use the article index to jump to a section. Long tables and technical records open on demand.
A. Task selection and evidence
We audited 260 historical patches across 122 software tasks. Exact-patch public execution logs were available for 216 patches across 78 tasks. We selected one candidate per task, first for 20 tasks and then for 40 different tasks, using fixed repository and report-length quotas. Selection did not use correctness labels or reviewer outcomes.
The combined sample covered six repositories and happened to contain 30 benchmark-passing and 30 benchmark-failing patches. This was not a correctness quota or a representative sample of all software work.
The factual summary retained adverse diagnostics; the full-report scenarios used identical log bodies. No new tests were generated or run. The reports came from the evaluation defining the official outcome, although its final resolved flag was not supplied as a direct answer key.
Detailed selection rules and evidence provenance
The source audit retained all 260 historical patches/122 tasks, distinguishing exact-patch public logs from merely available task tests. The eligible public-log pool contained 216 patches across 78 tasks. Each selected candidate's normalized patch SHA was checked against the public generator-specific patch; each report was bound to that source. Older inventory report locators were not reliable identifiers of generator-specific evidence, so scoring used verified same-directory official reports.
Phase 1 selected 20 tasks by repository/report-length quotas. Phase 2 selected 40 new tasks from the remaining 58, excluding all phase 1 tasks and the separate three-task development pilot. Fixed salted hashes ranked tasks within declared strata and ranked candidates separately within each chosen task. Selection used no correctness labels or reviewer outcomes. Repository quotas and fixed seeds yield reproducibility, not a simple random sample. Phase 1 covered six repositories; phase 2 covered four because the available seaborn/flask tasks were exhausted in phase 1. The combined roster has 30 passing/30 failing official labels by observation, not label stratification.
B uses batch 1's final deterministic ANSI-aware extraction; C/D/E use the complete raw execution log. Diagnostic retention does not mean every failure was caused by the candidate. No tests were generated or run for this experiment. The official resolved flag was not directly included as a reviewer answer key, but B–E expose evidence from its defining evaluation. This is not an independent benchmark-validation design.
B. Models, prompts, and collection
The reviewers were GPT-5.6 Terra, Claude Sonnet 5, and Gemini 3.8 Flash. Each task had two planned fresh reviews per model and scenario, with low reasoning effort, a 4,096-token output limit, and no tools. Each response supplied a decision, probability estimate, and short rationale.
Before the follow-up, Sonnet gained a native JSON output constraint to improve response formatting. The visible review instructions stayed fixed, but we cannot assume that the constraint had no behavioral effect. No study response was rerun or repaired.
| Stage | Tasks | Planned | Attempted | Usable | Invalid | Unattempted |
|---|---|---|---|---|---|---|
| Phase 1 | 20 | 600 | 600 | 563 | 37 | 0 |
| Phase 2 | 40 | 1200 | 1136 | 1129 | 7 | 64 |
| Total | 60 | 1800 | 1736 | 1692 | 44 | 64 |
“Usable” means a response could be interpreted in the required format, not that its decision was correct. Budget protection left 64 follow-up reviews unattempted. Invalid and unattempted responses remain missing, not rejections or wrong answers.
Exact model settings and collection details
Routes: openai/gpt-5.6-terra, anthropic/claude-sonnet-5, and google/gemini-3.8-flash. Each task/model/scenario has two planned fresh responses. Supplied reasoning effort was low; output cap 4096 tokens; no tools. The common question asks whether the patch should be accepted as the issue's solution. The exact system text, request bytes, responses and parser outputs remain in the original manifests.
Sonnet alone gained a native strict JSON-schema output constraint before phase 2. Ten checks on already-development tasks established format usability, not behavioral invariance. Visible review text stayed fixed, but provider-level constraints can alter effective context and outputs. Other routes were unchanged. This makes phase 2 a comparison of configured systems and makes cross-phase Sonnet synthesis a mixture of collection regimes. No study response was rerun or repaired.
| Stage | Tasks | Planned | Attempted | Usable | Invalid attempts | Unattempted |
|---|---|---|---|---|---|---|
| Phase 1 | 20 | 600 | 600 | 563 | 37 | 0 |
| Phase 2 | 40 | 1200 | 1136 | 1129 | 7 | 64 |
| Total | 60 | 1800 | 1736 | 1692 | 44 | 64 |
Usability means the preserved parser could extract an admissible decision/probability/rationale, not that the judgment was correct. Budget protection stopped phase 2 with 64 trailing planned cells unattempted. Development/format-check calls are outside these counts. Invalid and unattempted responses remain missing, not nonapprovals.
C. Decision definitions and metrics
The main findings distinguish willingness to approve from agreement with benchmark outcomes. These definitions explain the chart labels and the complete tables in Appendix E.
- Approval rate: the share of usable reviews that approved a patch, whether it passed or failed the benchmark.
- Passing-patch approval: approvals divided by usable reviews of benchmark-passing patches. This measures how often passing fixes were accepted.
- Failing-patch approval: approvals divided by usable reviews of benchmark-failing patches. This measures faulty approvals relative to the benchmark.
- Approval precision: among approved patches, the share that passed the benchmark. It is undefined if nothing was approved.
Hold means additional work or evidence was requested. Abstain means the reviewer could not meaningfully assess the patch. Neither is automatically a declaration that a patch is wrong, and an invalid or unattempted response is neither of these decisions.
The expanded tables also retain binary policy agreement: the share of usable reviews that either approved a passing patch or withheld a failing patch. Only for this metric are hold and abstain grouped together. It does not establish that every withheld patch needed more work.
Reviewers also supplied a probability that the patch satisfied the issue without relevant regressions. We do not use those estimates to claim calibrated confidence or to support the three main findings.
D. Statistical methods and uncertainty
Analysis history
The study examined all five scenarios. The earlier analysis plan prioritized the paired source-label comparison, E versus D. Comparing decisions with benchmark outcomes and the additional matched comparisons were added after response collection. We preserve that history without treating it as a ranking of the questions' importance.
Comparisons and uncertainty
For the source-label comparison, we compared the separate-evaluator and patch-producing-source scenarios within the same task and model, averaging the two repeats first. A task entered that comparison only when all four responses were usable. Pooled estimates required all three models and gave each model equal weight.
We resampled whole tasks 10,000 times to obtain the reported 95% bootstrap intervals. This preserves the relationship between repeated reviews of the same task. The intervals describe stability on our selected roster, not representativeness of all software tasks.
The supplementary model and evidence comparisons also use matched tasks. Finding 2 includes 35 follow-up tasks, of which 18 passed the benchmark and 17 failed. Finding 1 includes 15 benchmark-failing tasks with both repeats available across all three reviewers and scenarios A, B, and C. Missingness need not be random, and these cohorts are not interchangeable.
The charts retain conservative task-level uncertainty intervals, including nonzero upper bounds where no faulty approvals were observed. Full constructions and sensitivity results remain in the technical details below.
Could missing responses change Finding 1?
Missing responses do not erase the full-report-versus-no-report reduction on the planned roster. Across all 20 benchmark-failing follow-up tasks, every possible completion of missing decisions leaves fewer incorrect approvals under C than A: −17.5 to −2.5 percentage points for Terra, and −20.0 to −2.5 for Sonnet and Gemini. These are bounds on unobserved planned responses, not population confidence intervals.
In plain English, even assigning the missing decisions in the least favorable way would still leave fewer faulty approvals with the full report than without one on this planned task roster. That does not guarantee the same result on new tasks.
Full statistical definitions and sensitivity calculations
Source-label comparison
For E−D, average the two E approvals and subtract the average of the two D approvals within task/model. A complete block requires all four usable responses. Average task differences, not unmatched all-usable rates. Pooled estimates use only tasks complete across all three models and weight the models equally within task.
Original uncertainty resamples whole complete tasks 10,000 times with fixed seeds and reports 95% percentile intervals. It preserves within-task model/condition/repetition structure. These intervals describe exploratory stability on a quota-selected roster, not design-valid inference to all 78 tasks or all software work. Sparse effects can yield degenerate empirical intervals without proving equivalence.
Missing-answer bounds retain every planned task. For a condition with two repetitions, the lower approval mean is observed approvals/2; the upper mean adds missing responses/2. A task's E−D lower bound is lowerE−upperD; its upper bound is upperE−lowerD. Endpoints average over all planned tasks and, for pooled bounds, all three models. These are identification bounds conditional on observed answers, not sampling confidence intervals.
Any additional staged summary retains fixed planned phase weights: one-third phase 1 plus two-thirds phase 2. It does not weight toward whichever phase/model returned more usable responses. The article prioritizes the separate phase results.
Supplementary matched comparisons
The supplementary matched analysis matches models within each scenario, requiring both repetitions for all three. Follow-up common task counts are A 33, B 35, C 35, D 34 and E 32; these sets are not necessarily identical. Scenario comparisons instead match both conditions within one model. Both are supplementary analyses selected after collection, and completeness is not assumed random.
For a task with K approvals among two repeats, K/2 = [I(K≥1) + I(K=2)]/2. The class-rate intervals combine 97.5% exact binomial intervals for these two task-level indicators; a union bound gives conservative 95% coverage under independent, exchangeable task sampling without assuming independent repeats. With zero approvals on 17 tasks, the interval is 0–22.7%, not 0–0%. This sampling assumption is a sensitivity model, not established by the fixed quota design. The construction follows NIST's exact binomial interval definition ↗; the analyst's implementation was independently checked.
Paired supplementary differences resample whole tasks within benchmark-label class 10,000 times, preserving compared responses and the observed class mix. Intervals are not multiplicity-adjusted discoveries. They do not replace the original primary bootstrap. All C model comparisons below use 18 passing tasks, two reviews each:
| C passing-patch approval difference | Estimate | Task-bootstrap 95% interval |
|---|---|---|
| Sonnet − Terra | +16.7 points | +2.8 to +33.3 |
| Terra − Gemini | +25.0 points | +2.8 to +47.2 |
| Sonnet − Gemini | +41.7 points | +22.2 to +61.1 |
Full planned-response bounds answer a different question. For C, missing failing-patch responses allow rates from 0 to 7.5% in every reviewer. For C−A, every completion preserves a reduction: Terra −17.5 to −2.5 points; Sonnet and Gemini −20.0 to −2.5. These concern all 20 failing tasks and 40 planned responses per reviewer/scenario, not task-population uncertainty.
E. Complete results and missing responses
Approval across the five scenarios
| Evidence scenario | GPT-5.6 Terra | Claude Sonnet 5 | Gemini 3.8 Flash |
|---|---|---|---|
| No report | 39.5%30/76 reviews | 44.0%33/75 reviews | 20.3%15/74 reviews |
| Factual summary | 33.3%25/75 reviews | 39.5%30/76 reviews | 15.8%12/76 reviews |
| Full report · source unspecified | 35.5%27/76 reviews | 45.3%34/75 reviews | 23.7%18/76 reviews |
| Full report · patch-producing system | 32.9%25/76 reviews | 48.0%36/75 reviews | 26.7%20/75 reviews |
| Full report · separate evaluator | 35.5%27/76 reviews | 41.7%30/72 reviews | 27.6%21/76 reviews |
Approval is not accuracy: approving more patches can mean accepting more good work, more faulty work, or both. These follow-up rates use all usable responses, with different denominators. The main findings instead compare matched tasks. Metric definitions.
These tables support the approval overview and let readers inspect passing-patch approvals, faulty approvals, and withheld decisions for every model and scenario. They include all usable responses, so their denominators differ from the matched subsets used in Finding 1 and Finding 2. They should not be subtracted as if every row contained the same tasks.
Open either phase below. A means no report; B, factual summary; C, full report with source unspecified; D, the same report attributed to the patch-producing system; E, the identical report attributed to a separate evaluator. Metric definitions are in Appendix C, and missing-review counts are in Appendix B.
Phase 2: complete descriptive results
Scroll horizontally to see all metrics. These descriptive rates use all usable reviews, not a shared matched sample.
40 tasks; 20 benchmark-passing and 20 benchmark-failing. Eighty reviews planned per model-scenario. Repetitions are not independent observations. Numerators and denominators below are responses; rates do not impose a common task set across scenarios.
| Reviewer/scenario | Approval | Passing-patch approval | Failing-patch approval | Approval precision | Policy agreement | HOLD / ABSTAIN |
|---|---|---|---|---|---|---|
| Terra A | 39.5% (30/76) | 66.7% (26/39) | 10.8% (4/37) | 86.7% (26/30) | 77.6% (59/76) | 46 / 0 |
| Terra B | 33.3% (25/75) | 65.8% (25/38) | 0.0% (0/37) | 100.0% (25/25) | 82.7% (62/75) | 50 / 0 |
| Terra C | 35.5% (27/76) | 69.2% (27/39) | 0.0% (0/37) | 100.0% (27/27) | 84.2% (64/76) | 49 / 0 |
| Terra D | 32.9% (25/76) | 64.1% (25/39) | 0.0% (0/37) | 100.0% (25/25) | 81.6% (62/76) | 51 / 0 |
| Terra E | 35.5% (27/76) | 66.7% (26/39) | 2.7% (1/37) | 96.3% (26/27) | 81.6% (62/76) | 49 / 0 |
| Sonnet A | 44.0% (33/75) | 74.4% (29/39) | 11.1% (4/36) | 87.9% (29/33) | 81.3% (61/75) | 42 / 0 |
| Sonnet B | 39.5% (30/76) | 76.9% (30/39) | 0.0% (0/37) | 100.0% (30/30) | 88.2% (67/76) | 44 / 2 |
| Sonnet C | 45.3% (34/75) | 89.5% (34/38) | 0.0% (0/37) | 100.0% (34/34) | 94.7% (71/75) | 40 / 1 |
| Sonnet D | 48.0% (36/75) | 94.7% (36/38) | 0.0% (0/37) | 100.0% (36/36) | 97.3% (73/75) | 36 / 3 |
| Sonnet E | 41.7% (30/72) | 85.7% (30/35) | 0.0% (0/37) | 100.0% (30/30) | 93.1% (67/72) | 38 / 4 |
| Gemini A | 20.3% (15/74) | 28.9% (11/38) | 11.1% (4/36) | 73.3% (11/15) | 58.1% (43/74) | 59 / 0 |
| Gemini B | 15.8% (12/76) | 30.8% (12/39) | 0.0% (0/37) | 100.0% (12/12) | 64.5% (49/76) | 64 / 0 |
| Gemini C | 23.7% (18/76) | 46.2% (18/39) | 0.0% (0/37) | 100.0% (18/18) | 72.4% (55/76) | 58 / 0 |
| Gemini D | 26.7% (20/75) | 52.6% (20/38) | 0.0% (0/37) | 100.0% (20/20) | 76.0% (57/75) | 55 / 0 |
| Gemini E | 27.6% (21/76) | 53.8% (21/39) | 0.0% (0/37) | 100.0% (21/21) | 76.3% (58/76) | 55 / 0 |
Phase 1: complete descriptive results
Scroll horizontally to see all metrics. These descriptive rates use all usable reviews, not a shared matched sample.
20 tasks; 10 benchmark-passing and 10 benchmark-failing. Forty reviews planned per model-scenario. Repetitions are not independent observations. Numerators and denominators below are responses; rates do not impose a common task set across scenarios.
| Reviewer/scenario | Approval | Passing-patch approval | Failing-patch approval | Approval precision | Policy agreement | HOLD / ABSTAIN |
|---|---|---|---|---|---|---|
| Terra A | 40.0% (16/40) | 45.0% (9/20) | 35.0% (7/20) | 56.2% (9/16) | 55.0% (22/40) | 24 / 0 |
| Terra B | 40.0% (16/40) | 80.0% (16/20) | 0.0% (0/20) | 100.0% (16/16) | 90.0% (36/40) | 24 / 0 |
| Terra C | 22.5% (9/40) | 45.0% (9/20) | 0.0% (0/20) | 100.0% (9/9) | 72.5% (29/40) | 31 / 0 |
| Terra D | 25.0% (10/40) | 50.0% (10/20) | 0.0% (0/20) | 100.0% (10/10) | 75.0% (30/40) | 30 / 0 |
| Terra E | 40.0% (16/40) | 75.0% (15/20) | 5.0% (1/20) | 93.8% (15/16) | 85.0% (34/40) | 24 / 0 |
| Sonnet A | 35.3% (12/34) | 47.1% (8/17) | 23.5% (4/17) | 66.7% (8/12) | 61.8% (21/34) | 22 / 0 |
| Sonnet B | 40.6% (13/32) | 75.0% (12/16) | 6.2% (1/16) | 92.3% (12/13) | 84.4% (27/32) | 19 / 0 |
| Sonnet C | 51.5% (17/33) | 88.9% (16/18) | 6.7% (1/15) | 94.1% (16/17) | 90.9% (30/33) | 16 / 0 |
| Sonnet D | 44.1% (15/34) | 87.5% (14/16) | 5.6% (1/18) | 93.3% (14/15) | 91.2% (31/34) | 19 / 0 |
| Sonnet E | 48.5% (16/33) | 81.2% (13/16) | 17.6% (3/17) | 81.2% (13/16) | 81.8% (27/33) | 17 / 0 |
| Gemini A | 20.5% (8/39) | 21.1% (4/19) | 20.0% (4/20) | 50.0% (4/8) | 51.3% (20/39) | 31 / 0 |
| Gemini B | 15.0% (6/40) | 30.0% (6/20) | 0.0% (0/20) | 100.0% (6/6) | 65.0% (26/40) | 34 / 0 |
| Gemini C | 25.6% (10/39) | 52.6% (10/19) | 0.0% (0/20) | 100.0% (10/10) | 76.9% (30/39) | 29 / 0 |
| Gemini D | 25.6% (10/39) | 50.0% (10/20) | 0.0% (0/19) | 100.0% (10/10) | 74.4% (29/39) | 29 / 0 |
| Gemini E | 27.5% (11/40) | 55.0% (11/20) | 0.0% (0/20) | 100.0% (11/11) | 77.5% (31/40) | 29 / 0 |
F. Five-scenario walkthrough and patch examples
One patch, five review scenarios
Matplotlib 20676 concerns a plotting tool: turning on an interactive selection unexpectedly expanded the plot to include zero. The proposed patch changed how the selection handles were added so they would not change the plot limits. This was the first task in the frozen follow-up order, selected for illustration before benchmark scoring.
Every scenario used the same issue, candidate patch, review instructions, and final question: “Would you accept this patch as the solution to the supplied issue? Return the specified JSON object.” Reviewers could approve, hold, or abstain. They could not run tests or browse.
Read the original issue description
interactive SpanSelector incorrectly forces axes limits to include 0 <!--To help us understand and resolve your issue, please fill out the form to the best of your ability.--> <!--You can feel free to delete the sections that do not apply.--> ### Bug report **Bug summary** **Code for reproduction** <!--A minimum code snippet required to reproduce the bug. Please make sure to minimize the number of dependencies required, and provide any necessary plotted data. Avoid using threads, as Matplotlib is (explicitly) not thread-safe.--> ```python from matplotlib import pyplot as plt from matplotlib.widgets import SpanSelector fig, ax = plt.subplots() ax.plot([10, 20], [10, 20]) ss = SpanSelector(ax, print, "horizontal", interactive=True) plt.show() ``` **Actual outcome** The axes xlimits are expanded to include x=0. **Expected outcome** The axes xlimits remain at (10, 20) + margins, as was the case in Matplotlib 3.4 (with `interactive` replaced by its old name `span_stays`). attn @ericpre **Matplotlib version** <!--Please specify your platform and versions of the relevant libraries you are using:--> * Operating system: linux * Matplotlib version (`import matplotlib; print(matplotlib.__version__)`): master (3.5.0.dev1362+g57489bf19b) * Matplotlib backend (`print(matplotlib.get_backend())`): qt5agg * Python version: 39 * Jupyter version (if applicable): no * Other libraries: <!--Please tell us how you installed matplotlib and python e.g., from source, pip, conda--> <!--If you installed from conda, please specify which channel you used if not the default-->
Read the candidate patch supplied in all five scenarios
diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py
index a199e45d4..828e3331c 100644
--- a/lib/matplotlib/widgets.py
+++ b/lib/matplotlib/widgets.py
@@ -2366,10 +2366,21 @@ class ToolLineHandles:
line_props = {}
line_props.update({'visible': False, 'animated': useblit})
- line_fun = ax.axvline if self.direction == 'horizontal' else ax.axhline
self._line_props = line_props
- self.artists = [line_fun(p, **line_props) for p in positions]
+ if self.direction == 'horizontal':
+ trans = ax.get_xaxis_transform(which='grid')
+ else:
+ trans = ax.get_yaxis_transform(which='grid')
+
+ self.artists = []
+ for p in positions:
+ if self.direction == 'horizontal':
+ line = Line2D([p, p], [0, 1], transform=trans, **line_props)
+ else:
+ line = Line2D([0, 1], [p, p], transform=trans, **line_props)
+ self.ax.add_artist(line)
+ self.artists.append(line)
@property
def positions(self):
@@ -2463,7 +2474,7 @@ class ToolHandles:
'label': '_nolegend_',
**cbook.normalize_kwargs(marker_props, Line2D._alias_map)}
self._markers = Line2D(x, y, animated=useblit, **props)
- self.ax.add_line(self._markers)
+ self.ax.add_artist(self._markers)
self.artist = self._markers
@property
A. Issue and patch only
The reviewer received the issue and patch above, without a test report or report-source label. This shows what it decided from the proposed fix alone.
B. Add a factual summary
The reviewer also received the actual summary below, with the label “Report source: unspecified.” It retained the recorded failures, warnings, and diagnostic context. It reported three failures, 32 passes, and one skip. It was shorter than the full log, not a one-line verdict.
Read the exact factual summary supplied in scenario B
Factual execution summary. No overall patch-correctness or failure-causation conclusion is asserted.
Recorded test command(s):
pytest -rA lib/matplotlib/tests/test_widgets.py
Reported aggregate outcome line(s), verbatim:
=================== 3 failed, 32 passed, 1 skipped in 5.17s ====================
Adverse outcomes, warnings and diagnostic context retained verbatim:
Obtaining file:///testbed
Preparing metadata (setup.py): started
Preparing metadata (setup.py): finished with status 'done'
Requirement already satisfied: cycler>=0.10 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (0.12.1)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (1.4.7)
Requirement already satisfied: numpy>=1.17 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (1.24.4)
Requirement already satisfied: packaging>=20.0 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (24.2)
Requirement already satisfied: pillow>=6.2.0 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (10.4.0)
Requirement already satisfied: pyparsing>=2.2.1 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (2.4.7)
Requirement already satisfied: python-dateutil>=2.7 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (2.9.0.post0)
Requirement already satisfied: setuptools_scm>=4 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (8.2.0)
Requirement already satisfied: six>=1.5 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from python-dateutil>=2.7->matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (1.17.0)
Requirement already satisfied: setuptools>=61 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from setuptools_scm>=4->matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (75.1.0)
Requirement already satisfied: tomli>=1 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from setuptools_scm>=4->matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (2.2.1)
Requirement already satisfied: typing-extensions in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from setuptools_scm>=4->matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (4.12.2)
Installing collected packages: matplotlib
Attempting uninstall: matplotlib
Found existing installation: matplotlib 3.5.0.dev1371+g6786f437d
Uninstalling matplotlib-3.5.0.dev1371+g6786f437d:
Successfully uninstalled matplotlib-3.5.0.dev1371+g6786f437d
DEPRECATION: Legacy editable install of matplotlib==3.5.0.dev1372+g7f539777d.d20251120 from file:///testbed (setup.py develop) is deprecated. pip 25.0 will enforce this behaviour change. A possible replacement is to add a pyproject.toml or enable --use-pep517, and use setuptools >= 64. If the resulting installation is not behaving as expected, try using --config-settings editable_mode=compat. Please consult the setuptools documentation for more information. Discussion can be found at https://github.com/pypa/pip/issues/11457
Running setup.py develop for matplotlib
Successfully installed matplotlib
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable.It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.
[Non-diagnostic/setup or successful-test detail omitted.]
============================= test session starts ==============================
platform linux -- Python 3.8.20, pytest-8.3.4, pluggy-1.5.0
rootdir: /testbed
configfile: pytest.ini
plugins: xdist-3.6.1, cov-5.0.0, rerunfailures-14.0, timeout-2.3.1
collected 36 items
[Non-diagnostic/setup or successful-test detail omitted.]
=================================== FAILURES ===================================
___________________________ test_rectangle_selector ____________________________
def test_rectangle_selector():
check_rectangle()
with pytest.warns(
MatplotlibDeprecationWarning,
match="Support for drawtype='line' is deprecated"):
> check_rectangle(drawtype='line', useblit=False)
E matplotlib._api.deprecation.MatplotlibDeprecationWarning:
E The 'drawtype' parameter of __init__() was deprecated in Matplotlib 3.5 and will be removed two minor releases later. If any parameter follows 'drawtype', they should be passed as keyword, not positionally.
lib/matplotlib/tests/test_widgets.py:45: MatplotlibDeprecationWarning
_____________________ test_span_selector_bound[horizontal] _____________________
direction = 'horizontal'
@pytest.mark.parametrize('direction', ("horizontal", "vertical"))
def test_span_selector_bound(direction):
fig, ax = plt.subplots(1, 1)
ax.plot([10, 20], [10, 30])
ax.figure.canvas.draw()
x_bound = ax.get_xbound()
y_bound = ax.get_ybound()
tool = widgets.SpanSelector(ax, print, direction, interactive=True)
assert ax.get_xbound() == x_bound
assert ax.get_ybound() == y_bound
bound = x_bound if direction == 'horizontal' else y_bound
> assert tool._edge_handles.positions == list(bound)
E assert [0, 0] == [10.0, 20.0]
E
E At index 0 diff: 0 != 10.0
E Use -v to get more diff
lib/matplotlib/tests/test_widgets.py:318: AssertionError
______________________ test_span_selector_bound[vertical] ______________________
direction = 'vertical'
@pytest.mark.parametrize('direction', ("horizontal", "vertical"))
def test_span_selector_bound(direction):
fig, ax = plt.subplots(1, 1)
ax.plot([10, 20], [10, 30])
ax.figure.canvas.draw()
x_bound = ax.get_xbound()
y_bound = ax.get_ybound()
tool = widgets.SpanSelector(ax, print, direction, interactive=True)
assert ax.get_xbound() == x_bound
assert ax.get_ybound() == y_bound
bound = x_bound if direction == 'horizontal' else y_bound
> assert tool._edge_handles.positions == list(bound)
E assert [0, 0] == [10.0, 30.0]
E
E At index 0 diff: 0 != 10.0
E Use -v to get more diff
lib/matplotlib/tests/test_widgets.py:318: AssertionError
==================================== PASSES ====================================
=========================== short test summary info ============================
[Non-diagnostic/setup or successful-test detail omitted.]
SKIPPED [1] lib/matplotlib/testing/compare.py:274: Don't know how to convert .svg files to png
FAILED lib/matplotlib/tests/test_widgets.py::test_rectangle_selector - matplo...
FAILED lib/matplotlib/tests/test_widgets.py::test_span_selector_bound[horizontal]
FAILED lib/matplotlib/tests/test_widgets.py::test_span_selector_bound[vertical]
=================== 3 failed, 32 passed, 1 skipped in 5.17s ====================
Summary omissions: individual PASSED test names, shell-trace repetition and non-diagnostic setup/success detail. FAILURE/ERROR/warning sections, every non-pass test outcome line, and every diagnostic-bearing output paragraph are retained; diagnostic wording is not by itself proof that the candidate caused it.C. Supply the full report
The summary was replaced by the complete execution report, with the same label “Report source: unspecified.” This adds detail, including routine setup and individual passing-test names.
Read the full report shared by scenarios C, D, and E
Detailed test execution report. The following is the complete execution log; no lines have been omitted.
<execution_log>
+ source /opt/miniconda3/bin/activate
++ _CONDA_ROOT=/opt/miniconda3
++ . /opt/miniconda3/etc/profile.d/conda.sh
+++ export CONDA_EXE=/opt/miniconda3/bin/conda
+++ CONDA_EXE=/opt/miniconda3/bin/conda
+++ export _CE_M=
+++ _CE_M=
+++ export _CE_CONDA=
+++ _CE_CONDA=
+++ export CONDA_PYTHON_EXE=/opt/miniconda3/bin/python
+++ CONDA_PYTHON_EXE=/opt/miniconda3/bin/python
+++ '[' -z '' ']'
+++ export CONDA_SHLVL=0
+++ CONDA_SHLVL=0
+++ '[' -n '' ']'
+++++ dirname /opt/miniconda3/bin/conda
++++ dirname /opt/miniconda3/bin
+++ PATH=/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+++ export PATH
+++ '[' -z '' ']'
+++ PS1=
++ conda activate
++ local cmd=activate
++ case "$cmd" in
++ __conda_activate activate
++ '[' -n '' ']'
++ local ask_conda
+++ PS1=
+++ __conda_exe shell.posix activate
+++ /opt/miniconda3/bin/conda shell.posix activate
++ ask_conda='PS1='\''(base) '\''
export PATH='\''/opt/miniconda3/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'\''
export CONDA_PREFIX='\''/opt/miniconda3'\''
export CONDA_SHLVL='\''1'\''
export CONDA_DEFAULT_ENV='\''base'\''
export CONDA_PROMPT_MODIFIER='\''(base) '\''
export CONDA_EXE='\''/opt/miniconda3/bin/conda'\''
export _CE_M='\'''\''
export _CE_CONDA='\'''\''
export CONDA_PYTHON_EXE='\''/opt/miniconda3/bin/python'\'''
++ eval 'PS1='\''(base) '\''
export PATH='\''/opt/miniconda3/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'\''
export CONDA_PREFIX='\''/opt/miniconda3'\''
export CONDA_SHLVL='\''1'\''
export CONDA_DEFAULT_ENV='\''base'\''
export CONDA_PROMPT_MODIFIER='\''(base) '\''
export CONDA_EXE='\''/opt/miniconda3/bin/conda'\''
export _CE_M='\'''\''
export _CE_CONDA='\'''\''
export CONDA_PYTHON_EXE='\''/opt/miniconda3/bin/python'\'''
+++ PS1='(base) '
+++ export PATH=/opt/miniconda3/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+++ PATH=/opt/miniconda3/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+++ export CONDA_PREFIX=/opt/miniconda3
+++ CONDA_PREFIX=/opt/miniconda3
+++ export CONDA_SHLVL=1
+++ CONDA_SHLVL=1
+++ export CONDA_DEFAULT_ENV=base
+++ CONDA_DEFAULT_ENV=base
+++ export 'CONDA_PROMPT_MODIFIER=(base) '
+++ CONDA_PROMPT_MODIFIER='(base) '
+++ export CONDA_EXE=/opt/miniconda3/bin/conda
+++ CONDA_EXE=/opt/miniconda3/bin/conda
+++ export _CE_M=
+++ _CE_M=
+++ export _CE_CONDA=
+++ _CE_CONDA=
+++ export CONDA_PYTHON_EXE=/opt/miniconda3/bin/python
+++ CONDA_PYTHON_EXE=/opt/miniconda3/bin/python
++ __conda_hashr
++ '[' -n '' ']'
++ '[' -n '' ']'
++ hash -r
+ conda activate testbed
+ local cmd=activate
+ case "$cmd" in
+ __conda_activate activate testbed
+ '[' -n '' ']'
+ local ask_conda
++ PS1='(base) '
++ __conda_exe shell.posix activate testbed
++ /opt/miniconda3/bin/conda shell.posix activate testbed
+ ask_conda='PS1='\''(testbed) '\''
export PATH='\''/opt/miniconda3/envs/testbed/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'\''
export CONDA_PREFIX='\''/opt/miniconda3/envs/testbed'\''
export CONDA_SHLVL='\''2'\''
export CONDA_DEFAULT_ENV='\''testbed'\''
export CONDA_PROMPT_MODIFIER='\''(testbed) '\''
export CONDA_PREFIX_1='\''/opt/miniconda3'\''
export CONDA_EXE='\''/opt/miniconda3/bin/conda'\''
export _CE_M='\'''\''
export _CE_CONDA='\'''\''
export CONDA_PYTHON_EXE='\''/opt/miniconda3/bin/python'\'''
+ eval 'PS1='\''(testbed) '\''
export PATH='\''/opt/miniconda3/envs/testbed/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'\''
export CONDA_PREFIX='\''/opt/miniconda3/envs/testbed'\''
export CONDA_SHLVL='\''2'\''
export CONDA_DEFAULT_ENV='\''testbed'\''
export CONDA_PROMPT_MODIFIER='\''(testbed) '\''
export CONDA_PREFIX_1='\''/opt/miniconda3'\''
export CONDA_EXE='\''/opt/miniconda3/bin/conda'\''
export _CE_M='\'''\''
export _CE_CONDA='\'''\''
export CONDA_PYTHON_EXE='\''/opt/miniconda3/bin/python'\'''
++ PS1='(testbed) '
++ export PATH=/opt/miniconda3/envs/testbed/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
++ PATH=/opt/miniconda3/envs/testbed/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
++ export CONDA_PREFIX=/opt/miniconda3/envs/testbed
++ CONDA_PREFIX=/opt/miniconda3/envs/testbed
++ export CONDA_SHLVL=2
++ CONDA_SHLVL=2
++ export CONDA_DEFAULT_ENV=testbed
++ CONDA_DEFAULT_ENV=testbed
++ export 'CONDA_PROMPT_MODIFIER=(testbed) '
++ CONDA_PROMPT_MODIFIER='(testbed) '
++ export CONDA_PREFIX_1=/opt/miniconda3
++ CONDA_PREFIX_1=/opt/miniconda3
++ export CONDA_EXE=/opt/miniconda3/bin/conda
++ CONDA_EXE=/opt/miniconda3/bin/conda
++ export _CE_M=
++ _CE_M=
++ export _CE_CONDA=
++ _CE_CONDA=
++ export CONDA_PYTHON_EXE=/opt/miniconda3/bin/python
++ CONDA_PYTHON_EXE=/opt/miniconda3/bin/python
+ __conda_hashr
+ '[' -n '' ']'
+ '[' -n '' ']'
+ hash -r
+ cd /testbed
+ git config --global --add safe.directory /testbed
+ cd /testbed
+ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: lib/matplotlib/widgets.py
no changes added to commit (use "git add" and/or "git commit -a")
+ git show
commit 7f539777dce31a71e420092857d27a21a2e943ea
Author: SWE-bench <setup@swebench.config>
Date: Wed Sep 10 14:03:27 2025 +0000
SWE-bench
+ git -c core.fileMode=false diff 6786f437df54ca7780a047203cbcfaa1db8dc542
diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py
index a199e45d4..828e3331c 100644
--- a/lib/matplotlib/widgets.py
+++ b/lib/matplotlib/widgets.py
@@ -2366,10 +2366,21 @@ class ToolLineHandles:
line_props = {}
line_props.update({'visible': False, 'animated': useblit})
- line_fun = ax.axvline if self.direction == 'horizontal' else ax.axhline
self._line_props = line_props
- self.artists = [line_fun(p, **line_props) for p in positions]
+ if self.direction == 'horizontal':
+ trans = ax.get_xaxis_transform(which='grid')
+ else:
+ trans = ax.get_yaxis_transform(which='grid')
+
+ self.artists = []
+ for p in positions:
+ if self.direction == 'horizontal':
+ line = Line2D([p, p], [0, 1], transform=trans, **line_props)
+ else:
+ line = Line2D([0, 1], [p, p], transform=trans, **line_props)
+ self.ax.add_artist(line)
+ self.artists.append(line)
@property
def positions(self):
@@ -2463,7 +2474,7 @@ class ToolHandles:
'label': '_nolegend_',
**cbook.normalize_kwargs(marker_props, Line2D._alias_map)}
self._markers = Line2D(x, y, animated=useblit, **props)
- self.ax.add_line(self._markers)
+ self.ax.add_artist(self._markers)
self.artist = self._markers
@property
+ source /opt/miniconda3/bin/activate
++ _CONDA_ROOT=/opt/miniconda3
++ . /opt/miniconda3/etc/profile.d/conda.sh
+++ export CONDA_EXE=/opt/miniconda3/bin/conda
+++ CONDA_EXE=/opt/miniconda3/bin/conda
+++ export _CE_M=
+++ _CE_M=
+++ export _CE_CONDA=
+++ _CE_CONDA=
+++ export CONDA_PYTHON_EXE=/opt/miniconda3/bin/python
+++ CONDA_PYTHON_EXE=/opt/miniconda3/bin/python
+++ '[' -z x ']'
++ conda activate
++ local cmd=activate
++ case "$cmd" in
++ __conda_activate activate
++ '[' -n '' ']'
++ local ask_conda
+++ PS1='(testbed) '
+++ __conda_exe shell.posix activate
+++ /opt/miniconda3/bin/conda shell.posix activate
++ ask_conda='PS1='\''(base) '\''
export PATH='\''/opt/miniconda3/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'\''
export CONDA_PREFIX='\''/opt/miniconda3'\''
export CONDA_SHLVL='\''3'\''
export CONDA_DEFAULT_ENV='\''base'\''
export CONDA_PROMPT_MODIFIER='\''(base) '\''
export CONDA_PREFIX_2='\''/opt/miniconda3/envs/testbed'\''
export CONDA_EXE='\''/opt/miniconda3/bin/conda'\''
export _CE_M='\'''\''
export _CE_CONDA='\'''\''
export CONDA_PYTHON_EXE='\''/opt/miniconda3/bin/python'\'''
++ eval 'PS1='\''(base) '\''
export PATH='\''/opt/miniconda3/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'\''
export CONDA_PREFIX='\''/opt/miniconda3'\''
export CONDA_SHLVL='\''3'\''
export CONDA_DEFAULT_ENV='\''base'\''
export CONDA_PROMPT_MODIFIER='\''(base) '\''
export CONDA_PREFIX_2='\''/opt/miniconda3/envs/testbed'\''
export CONDA_EXE='\''/opt/miniconda3/bin/conda'\''
export _CE_M='\'''\''
export _CE_CONDA='\'''\''
export CONDA_PYTHON_EXE='\''/opt/miniconda3/bin/python'\'''
+++ PS1='(base) '
+++ export PATH=/opt/miniconda3/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+++ PATH=/opt/miniconda3/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+++ export CONDA_PREFIX=/opt/miniconda3
+++ CONDA_PREFIX=/opt/miniconda3
+++ export CONDA_SHLVL=3
+++ CONDA_SHLVL=3
+++ export CONDA_DEFAULT_ENV=base
+++ CONDA_DEFAULT_ENV=base
+++ export 'CONDA_PROMPT_MODIFIER=(base) '
+++ CONDA_PROMPT_MODIFIER='(base) '
+++ export CONDA_PREFIX_2=/opt/miniconda3/envs/testbed
+++ CONDA_PREFIX_2=/opt/miniconda3/envs/testbed
+++ export CONDA_EXE=/opt/miniconda3/bin/conda
+++ CONDA_EXE=/opt/miniconda3/bin/conda
+++ export _CE_M=
+++ _CE_M=
+++ export _CE_CONDA=
+++ _CE_CONDA=
+++ export CONDA_PYTHON_EXE=/opt/miniconda3/bin/python
+++ CONDA_PYTHON_EXE=/opt/miniconda3/bin/python
++ __conda_hashr
++ '[' -n '' ']'
++ '[' -n '' ']'
++ hash -r
+ conda activate testbed
+ local cmd=activate
+ case "$cmd" in
+ __conda_activate activate testbed
+ '[' -n '' ']'
+ local ask_conda
++ PS1='(base) '
++ __conda_exe shell.posix activate testbed
++ /opt/miniconda3/bin/conda shell.posix activate testbed
+ ask_conda='PS1='\''(testbed) '\''
export PATH='\''/opt/miniconda3/envs/testbed/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'\''
export CONDA_PREFIX='\''/opt/miniconda3/envs/testbed'\''
export CONDA_SHLVL='\''4'\''
export CONDA_DEFAULT_ENV='\''testbed'\''
export CONDA_PROMPT_MODIFIER='\''(testbed) '\''
export CONDA_PREFIX_3='\''/opt/miniconda3'\''
export CONDA_EXE='\''/opt/miniconda3/bin/conda'\''
export _CE_M='\'''\''
export _CE_CONDA='\'''\''
export CONDA_PYTHON_EXE='\''/opt/miniconda3/bin/python'\'''
+ eval 'PS1='\''(testbed) '\''
export PATH='\''/opt/miniconda3/envs/testbed/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'\''
export CONDA_PREFIX='\''/opt/miniconda3/envs/testbed'\''
export CONDA_SHLVL='\''4'\''
export CONDA_DEFAULT_ENV='\''testbed'\''
export CONDA_PROMPT_MODIFIER='\''(testbed) '\''
export CONDA_PREFIX_3='\''/opt/miniconda3'\''
export CONDA_EXE='\''/opt/miniconda3/bin/conda'\''
export _CE_M='\'''\''
export _CE_CONDA='\'''\''
export CONDA_PYTHON_EXE='\''/opt/miniconda3/bin/python'\'''
++ PS1='(testbed) '
++ export PATH=/opt/miniconda3/envs/testbed/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
++ PATH=/opt/miniconda3/envs/testbed/bin:/opt/miniconda3/condabin:/opt/miniconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
++ export CONDA_PREFIX=/opt/miniconda3/envs/testbed
++ CONDA_PREFIX=/opt/miniconda3/envs/testbed
++ export CONDA_SHLVL=4
++ CONDA_SHLVL=4
++ export CONDA_DEFAULT_ENV=testbed
++ CONDA_DEFAULT_ENV=testbed
++ export 'CONDA_PROMPT_MODIFIER=(testbed) '
++ CONDA_PROMPT_MODIFIER='(testbed) '
++ export CONDA_PREFIX_3=/opt/miniconda3
++ CONDA_PREFIX_3=/opt/miniconda3
++ export CONDA_EXE=/opt/miniconda3/bin/conda
++ CONDA_EXE=/opt/miniconda3/bin/conda
++ export _CE_M=
++ _CE_M=
++ export _CE_CONDA=
++ _CE_CONDA=
++ export CONDA_PYTHON_EXE=/opt/miniconda3/bin/python
++ CONDA_PYTHON_EXE=/opt/miniconda3/bin/python
+ __conda_hashr
+ '[' -n '' ']'
+ '[' -n '' ']'
+ hash -r
+ python -m pip install -e .
Obtaining file:///testbed
Preparing metadata (setup.py): started
Preparing metadata (setup.py): finished with status 'done'
Requirement already satisfied: cycler>=0.10 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (0.12.1)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (1.4.7)
Requirement already satisfied: numpy>=1.17 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (1.24.4)
Requirement already satisfied: packaging>=20.0 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (24.2)
Requirement already satisfied: pillow>=6.2.0 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (10.4.0)
Requirement already satisfied: pyparsing>=2.2.1 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (2.4.7)
Requirement already satisfied: python-dateutil>=2.7 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (2.9.0.post0)
Requirement already satisfied: setuptools_scm>=4 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (8.2.0)
Requirement already satisfied: six>=1.5 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from python-dateutil>=2.7->matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (1.17.0)
Requirement already satisfied: setuptools>=61 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from setuptools_scm>=4->matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (75.1.0)
Requirement already satisfied: tomli>=1 in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from setuptools_scm>=4->matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (2.2.1)
Requirement already satisfied: typing-extensions in /opt/miniconda3/envs/testbed/lib/python3.8/site-packages (from setuptools_scm>=4->matplotlib==3.5.0.dev1372+g7f539777d.d20251120) (4.12.2)
Installing collected packages: matplotlib
Attempting uninstall: matplotlib
Found existing installation: matplotlib 3.5.0.dev1371+g6786f437d
Uninstalling matplotlib-3.5.0.dev1371+g6786f437d:
Successfully uninstalled matplotlib-3.5.0.dev1371+g6786f437d
DEPRECATION: Legacy editable install of matplotlib==3.5.0.dev1372+g7f539777d.d20251120 from file:///testbed (setup.py develop) is deprecated. pip 25.0 will enforce this behaviour change. A possible replacement is to add a pyproject.toml or enable --use-pep517, and use setuptools >= 64. If the resulting installation is not behaving as expected, try using --config-settings editable_mode=compat. Please consult the setuptools documentation for more information. Discussion can be found at https://github.com/pypa/pip/issues/11457
Running setup.py develop for matplotlib
Successfully installed matplotlib
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable.It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.
+ git checkout 6786f437df54ca7780a047203cbcfaa1db8dc542 lib/matplotlib/tests/test_widgets.py
Updated 0 paths from 6bf93602e
+ git apply -v -
Checking patch lib/matplotlib/tests/test_widgets.py...
Applied patch lib/matplotlib/tests/test_widgets.py cleanly.
+ : '>>>>> Start Test Output'
+ pytest -rA lib/matplotlib/tests/test_widgets.py
============================= test session starts ==============================
platform linux -- Python 3.8.20, pytest-8.3.4, pluggy-1.5.0
rootdir: /testbed
configfile: pytest.ini
plugins: xdist-3.6.1, cov-5.0.0, rerunfailures-14.0, timeout-2.3.1
collected 36 items
lib/matplotlib/tests/test_widgets.py F.........FF..................s.... [ 97%]
. [100%]
=================================== FAILURES ===================================
___________________________ test_rectangle_selector ____________________________
def test_rectangle_selector():
check_rectangle()
with pytest.warns(
MatplotlibDeprecationWarning,
match="Support for drawtype='line' is deprecated"):
> check_rectangle(drawtype='line', useblit=False)
E matplotlib._api.deprecation.MatplotlibDeprecationWarning:
E The 'drawtype' parameter of __init__() was deprecated in Matplotlib 3.5 and will be removed two minor releases later. If any parameter follows 'drawtype', they should be passed as keyword, not positionally.
lib/matplotlib/tests/test_widgets.py:45: MatplotlibDeprecationWarning
_____________________ test_span_selector_bound[horizontal] _____________________
direction = 'horizontal'
@pytest.mark.parametrize('direction', ("horizontal", "vertical"))
def test_span_selector_bound(direction):
fig, ax = plt.subplots(1, 1)
ax.plot([10, 20], [10, 30])
ax.figure.canvas.draw()
x_bound = ax.get_xbound()
y_bound = ax.get_ybound()
tool = widgets.SpanSelector(ax, print, direction, interactive=True)
assert ax.get_xbound() == x_bound
assert ax.get_ybound() == y_bound
bound = x_bound if direction == 'horizontal' else y_bound
> assert tool._edge_handles.positions == list(bound)
E assert [0, 0] == [10.0, 20.0]
E
E At index 0 diff: 0 != 10.0
E Use -v to get more diff
lib/matplotlib/tests/test_widgets.py:318: AssertionError
______________________ test_span_selector_bound[vertical] ______________________
direction = 'vertical'
@pytest.mark.parametrize('direction', ("horizontal", "vertical"))
def test_span_selector_bound(direction):
fig, ax = plt.subplots(1, 1)
ax.plot([10, 20], [10, 30])
ax.figure.canvas.draw()
x_bound = ax.get_xbound()
y_bound = ax.get_ybound()
tool = widgets.SpanSelector(ax, print, direction, interactive=True)
assert ax.get_xbound() == x_bound
assert ax.get_ybound() == y_bound
bound = x_bound if direction == 'horizontal' else y_bound
> assert tool._edge_handles.positions == list(bound)
E assert [0, 0] == [10.0, 30.0]
E
E At index 0 diff: 0 != 10.0
E Use -v to get more diff
lib/matplotlib/tests/test_widgets.py:318: AssertionError
==================================== PASSES ====================================
=========================== short test summary info ============================
PASSED lib/matplotlib/tests/test_widgets.py::test_rectangle_drag[True-new_center0]
PASSED lib/matplotlib/tests/test_widgets.py::test_rectangle_drag[False-new_center1]
PASSED lib/matplotlib/tests/test_widgets.py::test_ellipse
PASSED lib/matplotlib/tests/test_widgets.py::test_rectangle_handles
PASSED lib/matplotlib/tests/test_widgets.py::test_span_selector
PASSED lib/matplotlib/tests/test_widgets.py::test_span_selector_drag[True]
PASSED lib/matplotlib/tests/test_widgets.py::test_span_selector_drag[False]
PASSED lib/matplotlib/tests/test_widgets.py::test_span_selector_direction
PASSED lib/matplotlib/tests/test_widgets.py::test_tool_line_handle
PASSED lib/matplotlib/tests/test_widgets.py::test_lasso_selector
PASSED lib/matplotlib/tests/test_widgets.py::test_CheckButtons
PASSED lib/matplotlib/tests/test_widgets.py::test_TextBox
PASSED lib/matplotlib/tests/test_widgets.py::test_check_radio_buttons_image[png]
PASSED lib/matplotlib/tests/test_widgets.py::test_check_bunch_of_radio_buttons[png]
PASSED lib/matplotlib/tests/test_widgets.py::test_slider_slidermin_slidermax_invalid
PASSED lib/matplotlib/tests/test_widgets.py::test_slider_slidermin_slidermax
PASSED lib/matplotlib/tests/test_widgets.py::test_slider_valmin_valmax
PASSED lib/matplotlib/tests/test_widgets.py::test_slider_valstep_snapping
PASSED lib/matplotlib/tests/test_widgets.py::test_slider_horizontal_vertical
PASSED lib/matplotlib/tests/test_widgets.py::test_range_slider[horizontal]
PASSED lib/matplotlib/tests/test_widgets.py::test_range_slider[vertical]
PASSED lib/matplotlib/tests/test_widgets.py::test_polygon_selector
PASSED lib/matplotlib/tests/test_widgets.py::test_MultiCursor[True-True]
PASSED lib/matplotlib/tests/test_widgets.py::test_MultiCursor[True-False]
PASSED lib/matplotlib/tests/test_widgets.py::test_MultiCursor[False-True]
PASSED lib/matplotlib/tests/test_widgets.py::test_rect_visibility[png]
PASSED lib/matplotlib/tests/test_widgets.py::test_rect_visibility[pdf]
PASSED lib/matplotlib/tests/test_widgets.py::test_polygon_selector_remove[1]
PASSED lib/matplotlib/tests/test_widgets.py::test_polygon_selector_remove[2]
PASSED lib/matplotlib/tests/test_widgets.py::test_polygon_selector_remove[3]
PASSED lib/matplotlib/tests/test_widgets.py::test_polygon_selector_remove_first_point
PASSED lib/matplotlib/tests/test_widgets.py::test_polygon_selector_redraw
SKIPPED [1] lib/matplotlib/testing/compare.py:274: Don't know how to convert .svg files to png
FAILED lib/matplotlib/tests/test_widgets.py::test_rectangle_selector - matplo...
FAILED lib/matplotlib/tests/test_widgets.py::test_span_selector_bound[horizontal]
FAILED lib/matplotlib/tests/test_widgets.py::test_span_selector_bound[vertical]
=================== 3 failed, 32 passed, 1 skipped in 5.17s ====================
+ : '>>>>> End Test Output'
+ git checkout 6786f437df54ca7780a047203cbcfaa1db8dc542 lib/matplotlib/tests/test_widgets.py
Updated 1 path from 6bf93602e
</execution_log>D and E. Change only the stated source
The issue, patch, and full report remained identical. Only the source line changed:
D. “Assigned report source for this scenario: the system that produced the patch.”
E. “Assigned report source for this scenario: a separate evaluator that did not produce the patch.”
These were hypothetical assignments, explicitly described as such in the review instructions. We did not obtain a second independent test report. C, D, and E used the same report text, verified against the saved evidence hashes.
What each reviewer decided
Each cell shows repeat 1 / repeat 2. All 30 reviews for this example were usable. Hold means further work was requested, not necessarily that the reviewer declared the patch incorrect.
| Scenario | GPT-5.6 Terra | Claude Sonnet 5 | Gemini 3.8 Flash |
|---|---|---|---|
| A. No report | Approve / Approve | Hold / Hold | Approve / Approve |
| B. Factual summary | Hold / Hold | Hold / Hold | Hold / Hold |
| C. Full report · source unspecified | Hold / Hold | Hold / Hold | Hold / Hold |
| D. Full report · patch-producing system | Hold / Hold | Hold / Hold | Hold / Hold |
| E. Full report · separate evaluator | Hold / Hold | Hold / Hold | Hold / Hold |
Without a report, Terra and Gemini approved both times; Sonnet held both times. With any report, all three held in both repeats. The report showed handle-position checks returning zero instead of the expected bounds, and the reviewers cited those failures. The candidate failed its official benchmark evaluation.
This example illustrates a change after supplying evidence, not an effect of the source label: D and E produced the same approval decisions here. We did not rerun the patch or independently establish the causal explanations in the reviewers’ rationales. The aggregate findings, not this single case, support the article’s conclusions.
A contrasting case: a passing fix that one reviewer withheld
A colorbar fix makes the distinction concrete. Matplotlib 22865 corrected missing divider lines beside extended colorbar ends. Its exact official evaluation was resolved, with 60 passing tests and one skip. Sonnet and Terra approved every scenario in both repetitions. Gemini held A/B/C both times but approved D/E both times.
In C, Gemini acknowledged the core fix while requesting removal of an added reproduction script and a regression test committed with the candidate. That was a stated acceptance requirement, not a declaration that the fix was incorrect. Its D/E approvals show that the requirement was not consistent across otherwise identical full reports.
This illustrative case was selected after outcomes by a declared rule: the first task ID with an official passing outcome, a favorable aggregate report, and a C withhold. Across 15 passing tasks with such reports, 26 of 86 usable C reviews withheld, spanning ten tasks. These are not 26 established reviewer errors.
This illustrates the distinction in Finding 2: passing the benchmark and satisfying a reviewer's acceptance requirements are not necessarily the same thing. We did not measure the necessity or cost of the extra work requested.
Reproducibility
Neither example substitutes for the aggregate analysis. The walkthrough above uses the first task in the frozen follow-up dispatch order, selected before benchmark scoring. The contrasting passing-patch example was selected after outcomes by the rule stated above. Exact patches, source reports, original responses, selection rules, and analysis code remain preserved in the research archive. Long identifiers and archival paths are retained in the expandable technical details. The public analysis supplement ↗ includes derived decisions, benchmark metadata, and code to reproduce the numbers. It excludes raw responses and source logs.
Source identifiers and reproducibility references
The first illustrative case, matplotlib__matplotlib-20676, was the preselected first-dispatch task; its patch SHA256 is a28ff586691989751dfc782a6e7b752233680442602b7c352f86c1992029130b. Its complete original-response audit is in existing-data-audit.json.
The contrasting matplotlib__matplotlib-22865 case was selected post hoc under the article's declared favorable-report/withhold filter; its patch SHA256 is 5fee2c157a1c4a647e449146e03ce71c4c0a7192d38a6708ff21a5239580557f. Exact public revisions, report hashes, all 30 decisions and original Gemini C1/C2 rationales are preserved in the local research archive. The case illustrates Finding 2. Gemini's description of the script as “untracked” was not adopted: the script is included in the submitted patch. Across all 20 benchmark-passing follow-up tasks, not only the favorable-report subset, 37 of 116 usable C responses withheld, across 14 tasks. These are descriptive policy decisions, not 37 proven review errors.
Primary artifacts remain in reports/research-program/verifier-study-1-20260901/evidence-attribution-batch1-20260911/ and evidence-attribution-batch2-20260912/. Public-label joins, report receipts and all-usable metric counts are in benchmark-label-analysis-20260912/benchmark-scoring.json; scoring code is score_existing.py. Article-specific matched calculations and figure data are preserved beside this article in the analysis/ and figures/ directories. The editorial source/claim map records exact supporting artifacts. No original classifications were changed for the article.
G. Limitations and interpretation
These limits apply to the three findings, rather than adding a separate research claim.
What the benchmark can establish
The reviewers saw reports from the same evaluation used to label patches as passing or failing. This tests interpretation of available benchmark evidence, not independent verification of software correctness. Tests can miss defects or reject acceptable implementations.
Where the results may not generalize
The sample was selected using repository and report-length quotas, not drawn to represent all software work. Public tasks may have appeared in model training. Missing responses also affect which tasks enter the matched comparisons. Results describe the selected tasks and configured reviewers.
Why the phases are not interchangeable
The follow-up used different tasks and repository proportions, and Sonnet gained a structured-output constraint. We cannot attribute a change between phases to only one of those differences. Appendix B records the configuration change; Appendix D explains how the phases were analyzed.
What was not measured
The source labels were hypothetical; no independently generated tests were compared. Reviewer explanations were not verified as accounts of their internal reasoning. We did not measure the necessity, time, or token cost of the additional work requested when approval was withheld.
References
- Don’t Judge Code by Its Cover: Exploring Biases in LLM Judges for Code Evaluation. Moon and colleagues, EACL Findings, 2026.
- Self- and Other-Labels Induce Bidirectional Bias in LLM Judges. arXiv:2608.18091, 2026.
- Are “Solved Issues” in SWE-bench Really Solved Correctly? An Empirical Study. arXiv:2503.15223, 2025.
- Why SWE-bench Verified no longer measures frontier coding capabilities. OpenAI, 2026.
