The agent didn't hallucinate. It ignored what the repo already knew. A developer's study of agent-authored pull requests in major .NET organizations found that the primary failure mode of AI code review is not hallucination but ignoring existing repository knowledge. Using the open-source review system review-pro, the developer pre-registered a study and analyzed three merged Copilot PRs, discovering that the tool's evidence-required review process caught issues that a single-pass review would miss. The findings challenge common assumptions about AI code review and highlight the importance of repository-aware review systems. I pre-registered a small study, pointed a 12-reviewer pipeline at three merged Copilot PRs in major .NET organisations, and the failure mode I found was not the one everyone talks about. Disclosure first: I maintain review-pro https://github.com/tufantunc/review-pro , the open-source review system used as the instrument here. This article exists because I wanted to know whether its premise survives contact with real data. Part of it didn't. That's in here too, along with the two findings I got wrong myself and the tool caught. You have an agent write a feature. You ask an agent to review it. What comes back is unhelpful in one of two directions: forty nitpicks, or a cheerful approval. The usual explanation for why AI-written code needs its own review discipline is hallucination — invented APIs, imports that don't exist, config keys nobody defined. My tool ships a reviewer dedicated to exactly that, so I had every incentive to find it. I went looking for it in the most honest place I could think of: agent-authored pull requests that real maintainers already merged into serious codebases. Before running anything, I wrote down what would count — a pre-registration with the corpus criteria, the classification buckets, and anti-gaming rules first run counts, no diff dropped after seeing its findings, false-positive rate published whatever it is, negative results published . The full document and per-case records are public; nothing below was decided after seeing results, and the two things that were are marked as dated amendments in it. The instrument: review-pro doesn't ask one LLM to review the diff. A triage pass reads the change and dispatches only the relevant specialist reviewers — each with a single mandate and its own scoped context — then a synthesis pass dedups overlapping findings, resolves conflicts by domain ownership, and emits one verdict. The rule that matters most for this study: reviewers are required to locate evidence in the repository before making a claim ; an unverified "this looks wrong" is forbidden by their rubric. diff └── triage — which reviewers does this change actually need? ├── correctness ┐ ├── api-contract │ only the relevant specialists, ├── tests │ in parallel — each must locate ├── craft │ repo evidence for every claim └── ai-antipatterns ┘ 12 exist; the chore diff got 4 └── synthesis — dedup, resolve conflicts, one verdict That's the shape under test, not just the tool: whether specialised, evidence-required review finds what a single diff-reading pass cannot. The corpus: merged PRs authored by GitHub's Copilot coding agent author:app/copilot-swe-agent — GitHub itself attributes them, no vibes-based "this looks AI-written" , in established organisations, 30–400 changed lines, taken by recency rather than by browsing for juicy diffs. Three cases: HEAD~2 -style ancestor parsing to the core lookup path. Merged with I anonymise the repos in this article because the argument is about a pattern, not about naming maintainers who merged something. The full records — PR links included — are published with the pre-registration https://github.com/tufantunc/review-pro/tree/main/studies/2026-08-copilot-pr-pilot , since "verified by hand" is only worth something if you can check it; the article body keeps them unnamed so the piece stays about the pattern, not the people. One observation before any review ran, recorded in the pre-registration because it cuts against my own thesis: merged agent PRs in serious orgs skew heavily mechanical. Version bumps, lint fixes, dependency updates, a disabled flaky test. If most agent code that actually ships is mechanical, the surface for "confidently invented an API" is narrower than the discourse assumes. My tool's AI-antipatterns reviewer has three falsifiable categories — claims that are objectively true or false, where I could verify every finding by hand: | Category | Case 1 | Case 2 | Case 3 | |---|---|---|---| | Hallucinated API / symbol / import | 0 | 0 | 0 | | Invented config / env key | 0 | 0 | 0 | | Needless dependency | 0 | 1 | 0 | Every symbol, import, and config key these agents wrote existed . I checked by hand; the reviewer checked independently; we agreed. The one hit was a dependency bump whose stated rationale didn't hold up — more on that below, because it's the most interesting defect of the lot. If you came here for "LLMs make up functions," this is a negative result, and I'm publishing it as one. In this corpus, merged agent code simply doesn't look like that. Tool access — agents grepping before they import — may well be part of why, but that's a hypothesis about mechanism, and this study didn't test it. The category that fired repeatedly — and produced every finding that would have changed a merge decision — was ignored convention . Not inventing things the codebase doesn't have. Failing to notice things it does. Case 1. The new ancestor-walking code calls a throwing API inside a method whose documented contract is "return null when not found." That alone is an ordinary bug. What makes it interesting: the repository already knows about this exact failure mode . There is a canonical guard elsewhere, with a comment that says, verbatim, "Our managed git implementation throws this on shallow clones." The agent's code sits outside that guard, so get-version HEAD~1 in a shallow CI clone regresses from a clean "bad ref" exit code to a raw internal error. Same story twice over: the repo knows how to peel annotated tags — there's a helper that does it — and the new code doesn't, so