# Diff-only versus codebase-aware review: what a diff hides

> Why many serious bugs sit outside the lines a pull request changes, three concrete examples, and how MergeHarbor indexes, traces and verifies to find them.

A pull request's diff is a precise record of what changed. It is not a record of what the change affects. The lines that break are often somewhere else: a caller in another package, a configuration reader, a service in another repository.

This guide shows what a diff leaves out, with three examples, and explains how a reviewer can see past it.

## What a diff shows, and what it leaves out

A diff shows the changed lines and a few lines of context around them. That is enough to judge local questions: is this condition inverted, is this loop off by one, is this variable unused.

It leaves out everything that depends on the change:

- **Callers.** Every place that calls a changed function and relies on its old behaviour.
- **Contracts.** Interfaces, types and API schemas that other code implements or consumes.
- **Configuration.** The code that reads a setting the change renamed, removed or re-typed.
- **Other repositories.** Services and libraries that share a contract with this one.
- **History.** Whether this code was reverted last month, or whether a similar fix was made elsewhere.

A reviewer that sees only the diff has two choices when a question depends on any of these: guess, or stay quiet. Guessing produces noise. Staying quiet produces misses.

## Example 1: a removed guard with distant callers

Here is a small change to a refund function. The diff removes a check that looks redundant:

```diff
 func Refund(charge Charge, amount int64) error {
-    if amount > charge.Amount {
-        return ErrRefundTooLarge
-    }
     return gateway.Refund(charge.ID, amount)
 }
```

Read alone, the change is plausible. Perhaps the caller already validates the amount. Whether that is true depends on code the diff does not show:

```go
// api/refunds.go — not in the diff
func handleRefund(w http.ResponseWriter, r *http.Request) {
    amount := parseAmount(r.FormValue("amount")) // customer input
    if err := pay.Refund(charge, amount); err != nil { … }
}
```

One caller passes the amount straight from a request. The guard was the only validation. A diff-only reviewer might say "make sure callers validate the amount", which is a hedge. A reviewer that looked up the callers can say which caller is unsafe and why, and suggest putting the guard back.

## Example 2: a changed contract

A function stops throwing when a record is missing and returns `undefined` instead:

```diff
 export async function findUser(id: string): Promise<User | undefined> {
   const row = await db.users.get(id);
-  if (!row) throw new NotFoundError(id);
-  return row;
+  return row ?? undefined;
 }
```

The type signature already allowed `undefined`, so the compiler is satisfied, and the author updated the caller they were working on. A caller in another module was written when the function always threw:

```ts
// billing/invoice.ts — not in the diff
const user = (await findUser(invoice.userId))!;
await sendReceipt(user.email); // now fails at runtime for deleted users
```

The non-null assertion was safe under the old contract and is wrong under the new one. Nothing in the diff hints at this file. Finding it requires knowing who calls `findUser` and what each caller assumes.

## Example 3: configuration and a second repository

A pull request renames a setting to make its unit explicit:

```diff
 # config/service.yaml
-timeout: 30
+timeout_seconds: 30
```

The application code that reads it is in a different file, and was not updated:

```python
# app/client.py — not in the diff
timeout = config.get("timeout", 0)  # 0 means "no timeout"
```

The service now runs with no timeout at all. Nothing fails in tests that do not exercise a slow dependency.

Contracts across repositories are the same problem one step further away. When a protobuf message loses a field, the services that read it may live in other repositories entirely. The diff in this repository cannot show them.

## How MergeHarbor sees past the diff

MergeHarbor's [codebase-aware review](https://mergeharbor.com/features/review-agent) is built around three steps: index the repository, trace what depends on the change, and verify each finding before posting it.

### Index

MergeHarbor indexes each repository into three structures:

- a **code graph** of definitions, calls and types;
- a **text index** for exact search;
- a **semantic index** for finding code by meaning.

After each push, only the changed files are indexed again. Reviews do not wait for the first index to finish: they start immediately, and context is added as indexing completes.

### Trace

From the functions, types and settings a pull request changes, MergeHarbor [follows the graph](https://mergeharbor.com/product#impact) to their callers, implementations and configuration readers, and ranks what to show within a fixed budget. The review summary states the blast radius: how many call sites, files and modules depend on the change, and whether tests cover them. Impact tracing runs on Deep and Max reviews, or when you turn it on.

[Related code](https://mergeharbor.com/product#context) comes from exact search, semantic search and graph neighbours. The results are merged, ranked, de-duplicated and trimmed to a token budget, so the reviewer sees the relevant parts without reading whole files.

### Investigate

The reviewer then works like a person would. It reads files, follows calls, searches the codebase and checks history, deciding its own next step. Its tools are read-only and scoped to the repository and commit under review. Deeper [review tiers](https://mergeharbor.com/docs/review-tiers) give it more steps, a stronger model and a larger context budget.

### Verify

Context makes findings possible; verification makes them trustworthy. A second pass [tries to disprove each finding](https://mergeharbor.com/product#verify), citing the code it checked, and disproved findings are never posted. In example 1, the verifier would check whether `handleRefund` or anything before it validates the amount. If it did, the finding would be dropped.

## Across repositories

Repositories that change together can be grouped into **clusters** under Memory → Cross-repo context. Every repository in a cluster can search the code and symbols of the others while it is reviewed, and MergeHarbor suggests clusters from people who open pull requests in several repositories. A repository can also name the repositories it depends on:

```yaml
# .mergeharbor/context.yml
files:
  - { path: /proto/payments/*.proto, reason: API contract, paths: ["api/**"] }
repos: [acme/shared-lib]
```

When a pull request edits an OpenAPI, protobuf or GraphQL contract, a [built-in check](https://mergeharbor.com/docs/migration-and-api-safety#api-contracts) compares it with the base version and lists every change that breaks an existing client. If the repository is in a cluster or has related repositories, the finding names the repositories whose indexed code uses the changed contract. See [cross-repository context](https://mergeharbor.com/docs/rules#cross-repository-context) for setup.

## What codebase context costs

Reading more code costs more tokens and takes longer. MergeHarbor keeps that in proportion:

- **Riskiest changes first.** Each part of the diff is ranked by what it touches before the model reads it, and tests, docs and formatting are only skimmed.
- **A tier per pull request.** Standard reviews the change and the code around it and investigates further when the change looks risky. Deep and Max always explore the repository. Auto picks a tier from size, risky areas, labels and history.
- **Budgets.** Context is trimmed to a token budget, generated and vendored files are left out, and you can cap credits per pull request.

On our [published benchmark](https://mergeharbor.com/benchmarks), the median time to a finished review was 18 seconds at Standard, 45 at Deep and 69 at Max. That benchmark ran with no repository index, so it measures the reviewer's tools alone; with an index, the reviewer has more to work with.

## When diff-only is enough

Not every change needs the whole codebase. Documentation, test-only changes, formatting and small self-contained edits are well served by a light review, and Auto always runs changes that only touch documentation or tests at Standard. The point is not to read everything every time. It is to be able to read what matters when the change reaches beyond its own lines.

## Further reading

- [How MergeHarbor reviews a pull request](https://mergeharbor.com/product#how-it-reviews)
- [Codebase-aware review](https://mergeharbor.com/features/review-agent) and [change risk](https://mergeharbor.com/features/change-risk)
- [Why the agent that wrote the code should not be its only reviewer](https://mergeharbor.com/guides/coding-agent-vs-independent-review)
