AI Code Review Catches Nulls and XSS Instantly — and Still Can't Tell You a Discount Makes Prices Negative
I Had AI Do a Complete Code Review of a Frontend Project — The Gap Between It and Humans Is Much Larger Than I Thought
Writing code with AI is 10x faster, but what happens after the code is written? Review becomes the new bottleneck. I had Claude Code do a full round of Code Review on a React project, and the result: on some issues it's far sharper than humans, and on others it makes mistakes more ridiculous than an intern's.
The Cause: A Mountain of Reviews Piling Up
After the team started using AI to write code, the number of PRs doubled. Code gets written fast, but the review speed hasn't kept up. Every day I open GitLab, and a dozen MRs awaiting review stare at me — each one hundreds of lines of AI-generated code.
A statistic from GitLab's AI Accountability Report: 85% of surveyed developers believe AI has already shifted the bottleneck from "writing code" to "reviewing code."
Since AI created this problem, can AI solve it itself?
I ran an experiment: I had Claude Code do a complete Code Review on a medium-sized React frontend project (about 200 components, 30,000 lines of code) to see what it could actually find and what it would miss.
5 Types of Problems AI Review Can Catch — Things Humans Really Tend to Miss
Let's start with the conclusion: in certain dimensions, AI Review is indeed better than humans. Not just a little better — a lot better.
1. Unhandled Edge Cases
This is AI's strongest area. It tirelessly checks every variable for the possibility of being null.
// AI flagged issue: data could be undefined
function UserProfile({ userId }) {
const { data } = useQuery(['user', userId], fetchUser);
return (
<div>
<h1>{data.name}</h1> {/* 💥 Explodes directly when data hasn't loaded */}
<p>{data.email}</p>
</div>
);
}
// AI suggested fix
function UserProfile({ userId }) {
const { data, isLoading, error } = useQuery(['user', userId], fetchUser);
if (isLoading) return <Skeleton />;
if (error) return <ErrorFallback error={error} />;
if (!data) return null;
return (
<div>
<h1>{data.name}</h1>
<p>{data.email}</p>
</div>
);
}
When humans review, seeing useQuery makes them default to "there must be loading state handling" and they often just glance over it. AI doesn't. It checks line by line whether every property access is safe.
2. Performance Anti-Patterns
AI is particularly sensitive to React re-rendering issues.
// AI flagged issue: creates a new object reference on every render
function Dashboard() {
const filters = { status: 'active', role: 'admin' }; // New object every render
return <UserList filters={filters} />; // UserList re-renders every time
}
// AI flagged issue: inline function defined inside map
function TodoList({ todos, onToggle }) {
return todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={() => onToggle(todo.id)} // New function every render
/>
));
}
These issues don't matter in small projects, but with many components they can make the page unbearably slow. Human reviewers rarely have the patience to check object references and callback function stability one by one; AI can do it.
3. Security Vulnerabilities
This is the part that gave me chills.
// AI flagged XSS risk
function Comment({ content }) {
return <div dangerouslySetInnerHTML={{ __html: content }} />;
}
// AI flagged issue: URL parameter directly concatenated, injection risk exists
function SearchPage() {
const query = new URLSearchParams(window.location.search).get('q');
fetch(`/api/search?q=${query}`) // Not encoded, injectable
.then(res => res.json())
.then(setResults);
}
There's a pretty frightening statistic: research shows that in AI-generated code, 61% is functionally correct, but only 10.5% is secure. In other words, the code runs, but it's full of holes.
AI Review is actually quite good at checking the security of code it wrote itself — it knows what mistakes it tends to make.
4. Naming and Consistency Issues
// AI flagged naming inconsistency
const getUserInfo = async (id) => { ... } // Uses Info
const fetchUserData = async (id) => { ... } // Uses Data
const loadUserDetail = async (id) => { ... } // Uses Detail
// AI flagged style inconsistency
const isActive = user.status === 'active'; // Boolean uses is prefix
const hasPermission = checkPermission(user); // Boolean uses has prefix
const canEdit = user.role === 'admin'; // Boolean uses can prefix
const userLoggedIn = !!token; // 💥 This one forgot the prefix
When humans review these issues, they often think "forget it, it runs." AI doesn't let them slide.
5. Duplicate Code and Extractable Common Logic
// AI found this loading + error handling pattern repeated in 14 components
function OrderList() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetchOrders()
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, []);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
// ...
}
// AI suggested extracting a custom Hook
function useAsync(asyncFn, deps = []) {
const [state, setState] = useState({
data: null, loading: true, error: null
});
useEffect(() => {
setState(prev => ({ ...prev, loading: true }));
asyncFn()
.then(data => setState({ data, loading: false, error: null }))
.catch(error => setState({ data: null, loading: false, error }));
}, deps);
return state;
}
AI can scan the entire project to find similar code snippets — something human reviewers can hardly do. When reviewing a single PR, you won't go flipping through other files to compare.
5 Fatal Blind Spots of AI Review — Things It Will Never Get Right
After the strengths, let's talk about where AI falls flat. Not minor slip-ups — the kind where following its advice would cause major trouble.
1. Whether Business Logic Is Correct — AI Simply Doesn't Understand Your Product
// AI thought this code was "fine"
function PriceDisplay({ price, discount }) {
const finalPrice = price - discount;
return <span>¥{finalPrice.toFixed(2)}</span>;
}
AI couldn't see the problem. But anyone who's worked on e-commerce knows immediately — finalPrice could be negative. When the discount is greater than the original price, the user sees ¥-15.00.
AI doesn't know business-level constraints like "discount cannot exceed original price" or "a negative price should display as 0." It only checks code logic, not business rules.
2. Architectural Decisions — Local Optimum ≠ Global Optimum
AI's suggestions for each file are correct individually, but together they can be a disaster.
AI's suggestions:
✅ "This component should use React.memo for optimization"
✅ "This data should be shared via Context"
✅ "This list should use virtual scrolling"
Actual situation:
If all three suggestions are implemented simultaneously, Context value changes → all memo components re-render
→ virtual scrolling state fully resets → user experience is worse than before optimization
AI can't see the dependency relationships and data flow between components. It finds the optimal solution for each part, but they don't add up to an optimal system.
3. User Experience and Interaction Details
// AI thought this code was fine
function DeleteButton({ onDelete }) {
return <button onClick={onDelete}>Delete</button>;
}
Problems AI can't detect:
- No confirmation dialog for delete action; accidental click and it's gone
- Button has no loading state; user doesn't know if deletion succeeded
- Rapid consecutive clicks trigger multiple delete requests
- No undo mechanism
These are all problems only someone who has used the product would know. AI hasn't used your product; it only sees the code.
4. Cross-Module Side Effects
// File A: User module
export function updateUserRole(userId, newRole) {
return api.patch(`/users/${userId}`, { role: newRole });
}
// File B: Permission module (AI can't see this when reviewing File A)
function PermissionGuard({ children, requiredRole }) {
const { user } = useAuth(); // Cached user data, won't auto-update
if (user.role !== requiredRole) return <Forbidden />;
return children;
}
User role is changed, but the permission guard uses cached data that won't refresh automatically. After the role change, the user still sees a "Forbidden" page until they refresh.
AI Review looks fine when reviewing a single file. But it won't tell you "changing this here will break that there" — because its context window can't fit the entire system.
5. "Correct but Harmful" Refactoring Suggestions
This is the most insidious trap. AI often suggests writing things in ways that "look better":
// Original code (AI thought it was "not elegant enough")
if (type === 'admin') {
return <AdminPanel />;
} else if (type === 'editor') {
return <EditorPanel />;
} else if (type === 'viewer') {
return <ViewerPanel />;
}
// AI's suggested "optimization"
const PANEL_MAP = {
admin: AdminPanel,
editor: EditorPanel,
viewer: ViewerPanel,
};
const Panel = PANEL_MAP[type];
return Panel ? <Panel /> : null;
Looks more elegant, right? But three months later, a new colleague needs to add a moderator role. They won't go digging through the PANEL_MAP constant — they'll try to add code where the else if originally was, then find they can't because the if-else has been "optimized" away.
Not all refactoring is good. Sometimes "dumb code" is easier to maintain than "clever code."
Practical Quick Reference: Which Reviews to Hand to AI, Which Must Be Manual
| Review Dimension | AI Capability | Human Capability | Verdict |
|---|---|---|---|
| Null/Boundary Checks | ⭐⭐⭐⭐⭐ | ⭐⭐ | Hand to AI |
| Performance Anti-Patterns | ⭐⭐⭐⭐ | ⭐⭐ | Hand to AI |
| Security Vulnerabilities (XSS/Injection) | ⭐⭐⭐⭐ | ⭐⭐⭐ | AI scans first, human reviews |
| Naming/Style Consistency | ⭐⭐⭐⭐⭐ | ⭐⭐ | Hand to AI |
| Duplicate Code Detection | ⭐⭐⭐⭐⭐ | ⭐ | Hand to AI |
| Business Logic Correctness | ⭐ | ⭐⭐⭐⭐⭐ | Must be manual |
| Architectural Soundness | ⭐⭐ | ⭐⭐⭐⭐ | Must be manual |
| User Experience Impact | ⭐ | ⭐⭐⭐⭐⭐ | Must be manual |
| Cross-Module Side Effects | ⭐ | ⭐⭐⭐⭐ | Must be manual |
| Whether Refactoring Is Worth It | ⭐⭐ | ⭐⭐⭐⭐ | Must be manual |
One-sentence summary: AI handles "is it correct," humans handle "should we do it this way."
My Current Review Process
After the experiment, I adjusted my review approach:
First pass: AI scan (done in 5 minutes)
- Edge cases, null checks
- Performance anti-patterns
- Security vulnerabilities
- Code style consistency
Second pass: Manual review (only looking at what AI can't handle)
- Is the requirement understood correctly?
- Is this solution the simplest one?
- Will changing this break something over there?
- Will users curse when they use it?
Before, a PR took half an hour to review. Now, after AI does the first pass, I only need 10 minutes to look at business logic and architectural decisions. Efficiency improved, and quality is actually higher — because AI handles all the mechanical checks that "get missed when attention slips."
But there's one iron rule: code that AI marks as "no problem" doesn't mean there really is no problem. It only means "no problem within what I can see." Whether the business logic is right, whether the architecture makes sense, whether the user experience is good — these will always be human work.
Has your team used AI for Code Review? Have you found any scenarios where AI is particularly good or particularly absurd? Let's chat in the comments.