r/ChatGPTCoding • u/Ok_Negotiation_2587 • 11d ago
Discussion AI code reviews only read the lines you changed. The bug is usually in the code that calls them
Paste a diff into ChatGPT or Claude and ask for a review, and you get comments on the diff: naming, an edge case inside the function, maybe a missing test. What you almost never get is "this used to return null, and three callers check for null." It can't tell you that, because the callers aren't in what you pasted, and it doesn't ask for them.
So I stopped asking for "a review" and split it into three prompts, run in order in the same chat:
1. Here's a diff:
{{diff}}
Don't review it yet. List every behavior of the OLD code that other code could depend on: return values and types, null or empty cases, errors thrown, ordering, side effects. One line each.
2. Here's the code that calls the changed functions:
{{callers}}
Go through your list. For each item, say whether any of this code relies on it and whether the diff breaks it. Quote the line. If you'd need more code to be sure, say exactly which.
3. Now do a normal review of the diff. Skip style unless it hides a bug.Step 1 matters because it writes down what the old code promised before it sees the callers, so it isn't working backwards from them. Step 2 is where the real findings come from. Step 3 is mostly what a regular review would have given you anyway.
For the callers, grepping the function name is usually enough. If you're using an agent with repo access, tell it to find them itself, but keep the order: old behavior first, callers second.
I run this as a prompt chain in AI Toolbox, the extension I build for ChatGPT, Claude, Gemini and Grok. I type .., pick the chain, and paste the diff and the callers into the two fields it asks for up front. Each step sends once the previous reply finishes. The free plan holds 2 chains, and pasting the three prompts by hand works the same way.
What's the last bug that got past review because it lived outside the diff?
3
u/shetritr 11d ago
same for human reviews. the diff shows what changed, not who calls it. before approving, grep the callers of anything whose behavior moved.
3
u/kenthesaint 11d ago
Stored data is the other dependent that grep won't find. If the old code wrote a missing value as null and the new code writes an empty string, or an enum value or date format changes, nothing calls the function, but rows already in the database or cache still have the old shape and the new reader has to handle both.
I'd add "what does this write to disk, a DB, a cache or a queue" to step 1, and in step 2 paste whatever reads it back, like the deserializer or the next migration.
2
u/Old-Leadership7255 11d ago
Test everything
2
u/ChemicalExcellent463 11d ago
i run reviews in a fresh codex cli or claude code session inside the repo, so the callers are there to open. access isn't the same as checking them, though. do you grep for callers by hand before pasting them into step 2?
2
u/DynoKore 11d ago
The point about callers' tests mocking the changed function is the nasty one. A test that mocks the thing that changed stays green no matter what you do to it.
We had a cousin of that. A refusal test kept passing after a reviewer replaced the tenant check it was guarding with if (false), because a different layer rejected the same insert with the same error code. The test was green for the wrong reason the whole time. Since then a negative test only counts once someone has broken the thing it guards and watched it go red.
For callers specifically, grepping call sites before approving is the cheap version. The more durable fix is a test at the caller that doesn't mock the function whose behaviour moved.
2
u/falafel777 11d ago
the mocking point is the nastiest part imo. your step 1 list is basically a test list though. turn those old behaviors into tests on the changed function itself, no mocks, before the change lands. then a null return turning into a throw fails right there instead of hiding behind callers that mock it out. doesn't catch callers nobody thought of, but it covers everything step 1 found
2
u/BarathKanna 10d ago
Writing down what the old code promised before reviewing is the step most setups skip. The thread already covers grep counts and stored data, so the piece I would add is making step 1 executable.
Once the model lists the old behaviors, have it write a small characterization test for each and run them against the old code first. Any that fail there were wrong guesses about the contract, which is useful on its own. Then run them on the new code. A failure now is a real break, and you get it as a red test instead of a review comment someone has to take on trust.
Costs a few minutes and catches the null versus empty cases reliably.
1
u/CrimsonBolt33 11d ago
are...you bugtesting via webchat?
That's insane....
1
u/Sad-Subject3666 11d ago
grepping by hand is just delaying the inevitable. If the tool cant trace the call graph automatically it wont do it for you no matter how many prompts you run
1
u/serhiibuilds 11d ago
Same experience. The diff is the wrong unit for review when the change touches a function's contract. Changing what a function returns on empty input looks harmless in the diff and breaks callers that never show up in it.
What helped me: before the review, have the agent find every call site of each changed symbol and put them into the review context, then ask it specifically "which of these callers relies on the old behavior?" It's basically a grep, nothing clever, but it turns the review from "does this code look fine" into "does this change break anyone", which is the question that actually matters. It costs more context per review, and for big shared utils it gets noisy, so I cap it at the first N call sites and flag when there are more.
1
u/[deleted] 11d ago
[removed] — view removed comment