SWE-bench's Hidden Flaw: Test-Passing ≠ Bug-Fixing
Your favorite coding agent scores 50% on SWE-bench Verified. That number gets cited in funding announcements, product comparisons, and architecture decisions. Should you trust it? The answer depends on what you think SWE-bench is measuring — and what it actually measures is subtler, and more concerning, than the leaderboard suggests.
Why This Matters
SWE-bench is the de facto benchmark for coding agents. Every major AI lab uses it to compare agent architectures. Decisions about tool selection, planning depth, context window usage, and retrieval strategy all get made in response to SWE-bench performance. If the benchmark is measuring something other than genuine software engineering ability, those decisions may be optimizing for the wrong objective.
What SWE-bench Actually Is
Jimenez et al., 2023 built SWE-bench from a straightforward insight: GitHub is full of resolved issues. A merged PR that closes an issue is a ground-truth example of "here is a bug description, here is a working fix." They scraped 2,294 such (issue, PR) pairs from 12 popular Python repositories — NumPy, scikit-learn, Django, Flask, and others.
Each task gives the agent three things: (1) the issue description, (2) the full repository at the state before the fix, and (3) the repository's existing test suite. The agent generates a patch. That patch gets evaluated by running the tests the original PR added or modified. Pass those tests and the task is "solved."
SWE-bench Verified is a 500-task subset where human experts at Anthropic and OpenAI manually confirmed two things: the tasks are genuinely solvable, and the test oracle validates the right behavior. It was designed to remove ambiguous or under-specified tasks from the original benchmark.
The evaluation pipeline looks clean on paper. Submit a patch, run tests, count passes. No human judges, no subjectivity, full reproducibility. But the simplicity of that pipeline is also where its assumptions break down.
Failure Mode 1: The Test Oracle Is Not the Bug
The first problem is epistemic: passing the tests is not the same as fixing the bug.
SWE-bench evaluates patches using the tests added or modified in the original PR. These tests verify a specific expected behavior — the behavior the PR author had in mind when writing the fix. But a software bug is rarely fully characterized by its test failures. A failing test is a symptom. The actual bug is a logic error, a race condition, an off-by-one in a code path that happens to trigger that symptom.
A minimal patch can satisfy the test oracle without addressing the root cause. Consider a function that returns None when it should return []. The test asserts the return value is not None. A principled fix handles the boundary condition correctly. A gaming fix returns [] unconditionally. Both pass the test. Only one fixes the underlying issue.
# The buggy function
def get_elements(lst, idx):
if idx >= len(lst):
return None # bug: should return []
return lst[idx:]
# Oracle test — what SWE-bench runs to evaluate correctness
assert get_elements([1, 2, 3], 5) is not None
# Gaming patch — passes the oracle, not a real fix
def get_elements(lst, idx):
if idx >= len(lst):
return [] # satisfies oracle
return lst[idx:] # still silently breaks on negative indices
# Correct patch — passes oracle AND handles edge cases
def get_elements(lst, idx):
if not 0 <= idx <= len(lst):
return []
return lst[idx:]
The gaming patch passes the oracle test. The correct patch passes the oracle test and does not silently mishandle negative indices. SWE-bench cannot tell the difference.
The root cause is oracle underspecification: the benchmark's evaluation signal is cheaper than the thing it's supposed to measure. The oracle tests cover the specific input that triggered the original report, not the full input space of the function. A patch that introduces a regression on an untested input looks identical to a correct patch from SWE-bench's perspective.
The theoretical ideal is a full behavioral specification — a comprehensive test suite that captures all intended behavior, not just the behavior the original PR was written to address. In practice, that specification does not exist. The original maintainer wrote tests for the behavior they cared about. A benchmark that reuses those tests inherits their incompleteness. And as agents get better at SWE-bench, the marginal gains increasingly come from learning to write minimal oracle-passing patches rather than from getting better at software engineering.
Failure Mode 2: Temporal Contamination
The second problem is statistical: a large fraction of the benchmark tasks may sit inside the training data of the models being evaluated.
SWE-bench tasks are scraped from public GitHub repositories. Those issues and PRs existed publicly before any training cutoff. For models trained through mid-2025 or later, many of the 2,294 original tasks — and likely a portion of the 500 Verified tasks — were present in training. The exact PR diff, the issue description, the failing test, even the expected output: potentially all seen during pretraining.
This is not hypothetical. Language models memorize long-tail training data at rates that correlate with repetition frequency. A popular repository like scikit-learn or Django gets indexed by multiple code datasets, mirrored on dozens of services, discussed in blog posts, referenced in Stack Overflow answers. Its issues and PRs appear many times across different training sources. The model does not need to have memorized the exact solution verbatim — recognizing the pattern of "this issue in this repo was fixed by this type of change" is enough to inflate performance.
Recent work interrogates this directly for SWE-bench Verified. The core methodology is temporal analysis: split tasks by whether the original issue was created before or after the model's training cutoff, then compare performance on each group. A model that generalizes well should score similarly on both. A model that is memorizing should score materially higher on pre-cutoff tasks.
# Contamination audit — conceptual implementation
def split_by_contamination(tasks, training_cutoff):
pre_cutoff = [t for t in tasks if t.issue_created_at < training_cutoff]
post_cutoff = [t for t in tasks if t.issue_created_at >= training_cutoff]
return pre_cutoff, post_cutoff
# A model that genuinely solves problems:
# score(pre_cutoff) ≈ score(post_cutoff)
#
# A model that recalls memorized solutions:
# score(pre_cutoff) >> score(post_cutoff)
Precise contamination measurements require training data disclosures that most labs do not publish in sufficient detail. But the structural vulnerability is undeniable. SWE-bench predates any serious community standard for temporal isolation between training and evaluation. The tasks were selected for quality and difficulty, not for their absence from pretraining data.
Failure Mode 3: Distribution Shift from Real Engineering
The third problem is distributional. SWE-bench samples from a specific slice of software engineering: open-source Python libraries, GitHub issue reports, PR-driven development. This slice is systematically over-represented in pretraining data relative to the actual problems practitioners face.
Real software engineering involves reading undocumented legacy code, debugging across language boundaries, understanding business logic that lives in people's heads rather than GitHub issues, making architectural changes that span dozens of files, and writing tests before writing the fix. SWE-bench tasks are, by construction, problems that could be described in a GitHub issue, were solvable via a single PR, and have existing test coverage that CI can run. That is a narrow and atypical slice of the actual work.
The Python bias compounds this. SWE-bench draws entirely from Python repositories. Python's dynamic nature and the specific idioms of scientific computing libraries create a distribution of bugs that looks different from what is common in enterprise codebases — typed languages, ORM-heavy applications, microservice architectures. An agent that excels at scikit-learn bugs may perform differently on TypeScript, Go, or Rust.
FeatBench measures a more realistic scenario: feature-level code generation where adding a feature correctly requires understanding existing design and not breaking adjacent functionality. The highest resolved rate on FeatBench reached 29.94%, even as SWE-bench Verified scores pushed past 40-50%. That gap is the distribution shift made concrete.
The Architecture of a Reliable Eval
Dynamic benchmarks are the most direct fix for temporal contamination. The principle: continuously generate new tasks from problems that post-date the model's training cutoff. A model cannot have memorized a task that did not exist at training time.
LiveCodeBench does this for competitive programming. It scrapes new contest problems continuously and maintains a time-stratified leaderboard. A model trained in 2024, evaluated on 2025 problems, faces a genuine out-of-distribution test.
SWE-EVO extends the principle to software engineering, using a rolling set of recent GitHub issues. The implementation challenge is significant: many post-cutoff tasks lack pre-existing tests, so oracle generation requires either relying on CI infrastructure or building automated test-generation pipelines.
The key addition that addresses both temporal contamination and oracle gaming is the regression check:
flowchart TD
A[GitHub issue created] --> B{After model training cutoff?}
B -- No --> C[Skip: contamination risk]
B -- Yes --> D[Snapshot repo at issue creation]
D --> E[Record baseline test failures]
E --> F[Task: generate patch to fix failures]
F --> G[Agent produces patch]
G --> H[Apply patch, run targeted tests]
H --> I{Targeted tests pass?}
I -- No --> J[Failed]
I -- Yes --> K[Run full test suite]
K --> L{New failures introduced?}
L -- Yes --> M[Failed: regression detected]
L -- No --> N[Solved]
Running the full test suite after applying the patch (step K) filters out gaming fixes: a patch that passes the targeted tests by breaking something else gets marked as failed. Standard SWE-bench skips this step entirely.
Measuring What Matters: A Practical Proposal
For practitioners building or selecting coding agents today, the reliable approach requires more effort than reading a leaderboard.
Temporal isolation. Restrict evaluation to tasks created after your candidate model's training cutoff. For models with mid-2024 cutoffs, use issues from late 2024 or 2025 onward. This single change does more for measurement validity than any other adjustment.
Regression testing. Run the full test suite before and after applying the agent's patch. A task counts as solved only if targeted tests pass and no previously-passing tests now fail. This directly filters oracle gaming.
Multi-task coherence. Sample multiple tasks from the same repository across different time windows. A model memorizing individual fixes will not generalize across related bugs in the same codebase. Correlated performance on related tasks is a stronger signal than total solve rate.
Calibrated human review. Manually review a 10% sample of oracle-passing patches. This is expensive but calibrates your oracle's false-positive rate. If 30% of reviewed patches are minimal gaming fixes rather than principled repairs, your reported solve rate is inflated by approximately that margin.
Cross-language and domain coverage. If your production codebase uses multiple languages or frameworks, benchmark across all of them. A model that scores well on Python-centric benchmarks may perform differently on TypeScript or Go.
Production-codebase tasks. Create benchmark tasks from your actual codebase — real bugs your team has fixed, with the ground-truth PR as the reference solution. This is the highest-fidelity measurement: it directly tests whether the agent helps you specifically.
Tradeoffs and What Does Not Work
Dynamic benchmarks eliminate temporal contamination but leave oracle underspecification intact. Post-cutoff tasks still get evaluated by running a test suite. Gaming patches still pass.
Human review does not scale to continuous evaluation. It is useful for calibration and periodic audits, not as a primary benchmark signal.
Stricter oracles introduce false negatives. A correct bug fix can fail an existing test if that test was itself testing buggy behavior. This happens in practice: sometimes the existing tests are wrong, and the correct fix requires changing a test. SWE-bench Verified's curation tried to filter these cases but cannot eliminate them entirely at scale.
Synthetic oracle generation is gameable in a different way. If you automatically generate tests for each task, models can learn to satisfy oracle-generation heuristics rather than fixing bugs. Goodhart's law applies directly: once a measure becomes a target, it ceases to be a good measure.
The Practitioner's Lens
Do not use the public SWE-bench leaderboard as your primary signal for selecting a coding agent backend. It tells you something about a model's general code-comprehension ability, shaped by what it has memorized from GitHub. It does not predict performance on your codebase, written by your developers, after your model's training cutoff.
Build a private eval. Use problems from your target codebase, created post-cutoff, with a regression check. Sample 10% for human review to understand your oracle's reliability. This is more effort than reading a leaderboard, but it produces a signal you can act on.
SWE-bench was a genuine contribution. It moved evaluation from toy problems to real software engineering at a reproducible scale. The problem is that the community has vested enormous trust in the solve rate — a number that conflates memorization, oracle gaming, and genuine problem-solving ability in ways that matter more as agents improve and as the gap between benchmark performance and production performance becomes harder to ignore.
The next frontier is not a higher SWE-bench score. It is building evals that force the distinction.
Further Reading
- SWE-bench: Can Language Models Resolve Real-World GitHub Issues? — Jimenez et al., 2023
- Does SWE-Bench-Verified Test Agent Ability or Model Memory? — contamination analysis, December 2025
- FeatBench: Towards More Realistic Evaluation of Feature-level Code Generation
- SWE-EVO: Benchmarking Coding Agents in Evolving Codebases
- LiveCodeBench — dynamic, contamination-free competitive programming eval