AI Code Review Academy has practical material worth checking out:
The whole thing is public if you want to follow along: PR #138.
Connecting Qodo to the repo
I started on the free Developer plan.
- Create an account at qodo.ai/get-started.
- Install the Git plugin and authorise the repository. Ozigi is on GitHub. Qodo also supports GitLab, Bitbucket, and Azure DevOps.
- After that, every pull request gets reviewed when it opens. There is also an IDE plugin (VS Code and JetBrains) and a CLI plugin if you want the review inline or before you push.
The first thing Qodo posted on PR #138 was a header: 3 bugs, 0 rule violations, 1 compliance rule applied. No setup beyond the install. It had already indexed the codebase, which matters for one of the findings below in a way a diff-only tool cannot match.
Qodo comments
The Three Problems that Qodo Identified
The badges that could hijack my own tab
There is a problem usually created or missed by AI-generated code: if you open a link in a new tab with target="_blank" and, without the right rel, the new page gets a handle to your original tab through window.opener. It can quietly redirect that tab somewhere else. Tis is called reverse-tabnabbing. AI-generated UI omits the guard against it constantly because the markup looks complete without it.
However, Qodo flagged a security bug on the landing page: external target="_blank" links missing rel="noopener noreferrer".
Then it did the part I did not expect. It read each link separately and told me which guard each one was missing.
This matters because this is an actual vulnerability on a page that real visitors load, and it shipped inside a PR that was mostly about copy and mobile spacing. Very easy to waive through.
The launch badge strip on our website had several offenders.
This is what the code looked like before the review:
// app/page.tsx (before)
<a href="https://theresanaiforthat.com/ai/ozigi/?ref=featured" target="_blank" rel="nofollow">
<img src="https://media.theresanaiforthat.com/featured-on-taaft.png?width=600"
alt="Featured on TAAFT" className="h-7 w-auto ..." />
</a>
<a href="https://startupfa.me/s/ozigi?utm_source=ozigi.app" target="_blank">
<img src="https://startupfa.me/badges/featured-badge-small.webp"
alt="Featured on Startup Fame" className="h-7 w-auto ..." />
</a>
The fix is actually small once someone or something points at it. Every external target="_blank" gets the full guard, and you keep nofollow where you want it:
// app/page.tsx (after)
<a href="https://theresanaiforthat.com/ai/ozigi/?ref=featured"
target="_blank" rel="noopener noreferrer nofollow">
...
</a>
<a href="https://startupfa.me/s/ozigi?utm_source=ozigi.app"
target="_blank" rel="noopener noreferrer">
...
</a>
Qodo even caught the half-measure: GoodAITools already had rel="noopener" but was missing noreferrer. That is the level of reading I would not give a badge strip at the end of a long PR.
ref no opener
More than pinpointing the exact areas where your code is vulnerable or wrong, Qodo goes ahead to give prompt suggestions to your preferred AI agent to fix it.
This entirely impressed me.
The sitemap that pointed crawlers at a page I never built
Another problem that was discovered: A sitemap is a promise to search engines. It works in the way that tells a search engine, "These URLs exist; go index them." If this is not satisfied, you spend crawl budget on 404s and collect sitemap errors. The problem is that a wrong URL in a sitemap looks completely valid on its own. You have to know the app's routing to know it is wrong.
Here’s what Qodo did: It caught a correctness bug: the blog sitemap added ${baseUrl}/blog as a static page, but there is no /blog index route in the blog app. It worked this out by reading across files, not by looking at the diff in isolation.
This matters because this is the finding that proves the codebase-context claim on Qodo. To decide that /blog does not exist, Qodo cross-referenced the blog listing page at apps/blog/app/page.tsx (which lives at /), the post route at apps/blog/app/blog/[slug]/page.tsx, and the top-level slug route at apps/blog/app/[slug]/page.tsx. None of those define a /blog index. The new sitemap entry was the only place that the route was ever named.
The Ozigi example. The offending lines:
// apps/blog/app/sitemap.ts
{
url: `${baseUrl}/blog`, // there is no /blog index route in this app
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.9,
},
And the routing that proves the point, straight from the repo:
apps/blog/app/page.tsx -> / (the blog listing)
apps/blog/app/[slug]/page.tsx -> /<slug>
apps/blog/app/blog/[slug]/page.tsx-> /blog/<slug> (individual posts)
# no apps/blog/app/blog/page.tsx, so /blog returns nothing
A line-level reviewer only sees a tidy sitemap object and approves it. Qodo read the routes, found the gap, and gave me two ways out: build the /blog index, or drop the URL. I could not have caught this by staring at the diff, because the diff was correct. The codebase was the thing that disagreed with it.
qodo success
The imports wired to nothing
The problem here is that AI writes the scaffolding for a feature, then never wires it up. You are left with imports that point at code nothing calls. Dead weight that lint will eventually fail the build over.
Here’s what Qodo did: It flagged a maintainability bug. app/sitemap.ts imports changelog and TUTORIALS and never uses either. It backed the call by reading the project's own lint setup.
This matters because this one would not page you at 2am, but it fails CI the moment next lint runs, and it loads modules the route never needs.
The Ozigi example:
// app/sitemap.ts
import { MetadataRoute } from "next";
import { changelog } from "@/lib/changelog"; // never referenced
import { TUTORIALS } from "@/lib/tutorials"; // never referenced
const BASE = "https://ozigi.app";
What sold me was the evidence Qodo attached. It did not just say "unused import." It pointed at eslint.config.mjs, which extends next/typescript, and at the lint script in package.json, to show that this repo is configured to treat exactly this as an error. The finding came with proof.
Qodo success
The build that would not install
Here is the finding that changed how I think about the tool entirely.
Qodo did not stop at reviewing the code, it also read the failed CI run.
The problem. A pull request can be perfectly written and still be unmergeable because the dependency graph will not resolve. The error lands in a CI log most people skim and move past.
Qodo posted a separate analysis of the failed GitHub Actions job and named the exact conflict. npm install died with ERESOLVE. mem0ai wants one major version of the Anthropic SDK as a peer; @copilotkit/runtime wants a newer one. npm could not satisfy both:
npm error code ERESOLVE
npm error While resolving: @copilotkit/runtime@1.59.5
npm error Found: @anthropic-ai/sdk@0.40.1
npm error peer @anthropic-ai/sdk@"^0.40.1" from mem0ai@2.4.6
npm error Could not resolve dependency:
npm error peerOptional @anthropic-ai/sdk@"^0.57.0" from @copilotkit/runtime@1.59.5
I checked. Both packages are in my package.json (mem0ai@^2.4.4 and @copilotkit/runtime@^1.56.5), so the conflict wasn’t a hallucination, or a fluke of the runner. Qodo also noted that a later submodule warning in the same log was secondary, not the cause. It read the whole failure and told me where to look.
This matters so much because even though the PR's code was fine, the PR still could not ship. A reviewer who only reads diffs would have missed this entirely, because the problem was not in any line I changed.
Qodo feedback
qodo code feedback
The rule layer and the rules I built myself
The header on the review said 1 compliance rule applied and 0 violations. My code passed the rule layer.
Inside Ozigi, I built a small rule system of my own. It is a banned-word list that runs every time the app generates content, so nothing reads like default AI prose:
// lib/prompts/anti-ai.ts (excerpt)
export const ANTI_AI_RULES = `
## THE BANNED LEXICON
delve, tapestry, robust, seamless, leverage, crucial, vital, comprehensive,
unlock, empower, streamline, ...
`;
A validator scores every draft against that list and rewrites anything that trips it. I was proud of it. But I’ve come to see that it is the toy version of what Qodo's rule system does.
Mine checks prose against words I typed out by hand. Qodo's rule system works at the level of the codebase: naming conventions, route structure, logging expectations, architecture rules, team standards, derived from the repo and enforced on every review. On this PR it ran one platform compliance rule and my changes cleared it. On a PR that breaks a convention, that is the layer that would stop it. Same idea I had, applied to code instead of copy, and to a whole project instead of a word list.
Qodo also went a level up without being asked. In its PR summary it suggested an approach I had not considered: pull the shared positioning strings (the headline, the CTA, the channel names) into one module, because right now they are duplicated across app/page.tsx and app/opengraph-image.tsx and will drift. That is not a bug. It is the kind of note a senior reviewer leaves.
Qodo summary diagram
Qodo explainer
How Qodo Reviewed my PR, in plain terms
| Engineering problem |
What Qodo did in Ozigi |
Why it mattered |
| AI-generated UI often omits link safety |
Flagged external target="_blank" links missing rel="noopener noreferrer" on the landing page, link by link |
Closed a reverse tab-nabbing vector before it shipped to real visitors |
| A sitemap can promise routes that do not exist |
Caught a /blog entry by cross-referencing the blog's actual routes across three files |
Stopped crawlers from being sent to a 404 and the SEO errors that follow |
| AI leaves scaffolding, it never wires up |
Identified unused imports in app/sitemap.ts and cited the repo's own lint config as proof |
Removed code that would fail next lint and load modules for nothing |
| A PR can be correct and still unmergeable |
Read the failed CI run and named the exact ERESOLVE dependency conflict |
Explained why the build broke when the diff itself was clean |
| Fast AI development creates code that runs, but does not fit the system |
Reviewed the PR with full codebase context and a compliance rule |
Acted as an independent review layer for AI-generated code |
Observations After The Run:
The unused-imports bug is the one I would argue about. That sitemap refactor intentionally left commented placeholders for future per-changelog and per-tutorial routes, and the changelog and TUTORIALS imports were me staging the next step rather than dead code I forgot. Qodo is still correct that, as written, lint will fail on them. So the right move is probably to wire them up or remove them now and add them back when the routes land, not to leave them sitting unused. I landed on Qodo's side in the end. I just did not think it was a bug at first glance.
Rounding Up
Writing this PR took an AI a few minutes. However, finding the security hole, the broken sitemap URL, the dead imports, and the reason the build would not install took a separate layer that reads the whole codebase, the routing, the lint config, and the CI log is the work now.
That is the role Qodo played here. It sat on top of code I generated and reviewed it for what the generator could not see: safety, consistency with the rest of the app, and whether it could actually ship. Five findings, all real, all in one public PR, none of them visible from the diff alone.
For a sense of what this looks like past a solo project, Qodo reports Fortune 100 customers saving more than 450,000 developer hours a year with this review layer in place. My version of the win was smaller. I did not ship a tab-hijack vector to my own landing page.
If you are generating code faster than you can review it, point Qodo at your messiest repo and open a real PR.