Mike Cann's avatar
Mike Cann
a month ago

Claude Code vs Codex: which is best?

Which agent is better at coding, Claude Code or Codex? To find out, I ran a Claude Code vs. Codex comparison the hard way. I built the same app six times, three runs with Claude Code and three with Codex. For each agent I built once on Convex, once on Supabase, and once on Firebase. I wanted to know three things: which agent produced the best-quality code, which produced the most secure code, and which backend is actually best for AI agents to work with. Finding out took a lot longer than I expected.

Methodology

The pipeline had four stages: build, functional evaluation, security evaluation, and code quality evaluation.

The benchmark methodology as a pipelineThe benchmark methodology as a pipeline

For the build step, each agent built a collaborative project management web app from scratch, once per backend, using React, Vite, and TypeScript. That's Claude Code on Opus 4.8 high and Codex on GPT-5.5 high. To keep the comparison fair, the only thing that changed between runs was the backend name in the prompt. Each agent ran through the API inside an isolated Docker container, so no local skills or memories could leak in. Each container came with a pre-authenticated CLI for Convex, Supabase, or Firebase, which kept builds repeatable and disposable. Every run got 45 minutes, which I assumed would be plenty. It wasn't, in some cases.

I picked a project management app because it stresses all three backends at once: authentication, roles, organizations with members (admin, member, viewer), projects, Kanban-style boards, tasks with descriptions and due dates, comments, and file uploads. That's enough surface area to get real signal on how each model handles a backend it's given.

Once a build finished, I ran a manual functional evaluation against a 40-item checklist. The kinds of things it checked:

  • Does the app open without a fatal error?
  • Can an assigned user create a board and move tasks?
  • Can a viewer read but not edit?
  • Does real-time collaboration actually work (user A updates something and user B sees it without refreshing)?
  • Do file uploads work?
  • Does the activity feed populate?

Each item got a pass, fail, or blocked if something upstream was broken. If a build didn't run at all, I gave the agent one repair attempt. I handed it the container state, described what was broken, asked it to fix it, then re-ran the checklist.

I evaluated functional behavior manually because I couldn't get automated evaluation to work reliably, which I'll come back to.

Security and code quality evaluation were both automated, using isolated Docker containers separate from the ones the agents built in. I had Claude and Codex each run two security reviews grounded in the official Convex, Firebase, and Supabase documentation, auditing for the kind of access-control mistakes that show up in real apps. When the two runs or two models disagreed, I adjudicated by hand. That mostly meant resolving disagreements about severity, not about whether an issue existed at all. That produced a letter grade from A to F. Code quality worked the same way: an isolated container graded architecture, modularity, backend idioms, type safety, data flow, front-end state, and error handling, run twice per model with human adjudication on disagreements.

One thing helped both evaluations. I forced the grading models to enumerate every file in the generated source tree and walk through them one by one, using each file as an entry point to trace data flow. That surfaced issues a surface-level pass would have missed.

Every input, output, and adjudication from this benchmark is public in the agent-benchmarks repo. That includes the functional behavior checklist, the security adjudication, and the code quality adjudication, so you can check my work or rerun it yourself.

The results, backend by backend

Here's how all six Claude Code vs. Codex builds scored:

BackendAgentFunctionalSecurityCode qualityTimeCost
ConvexClaude Code40/40, no repairsBB27 min$6.49
ConvexCodex40/40BC~17 min~$9.56 (est.)
SupabaseClaude Code40/40, no repairsCB40 min$6.24
SupabaseCodex40/40, 1 repair (env vars)CC26 min$5.68
FirebaseClaude Codetimed out, unverifiedDB>45 min$9.85
FirebaseCodex34/40, 3 repairs (file uploads)DC34m 25s$9.74

Convex and Supabase held up cleanly, and every agent hit a perfect 40/40 on both. Firebase is where things fell apart. Claude Code blew past its 45-minute window and never gave me a run I could verify functionally, iterating the whole time, and Codex only reached 34/40 after three repair attempts. Both landed at a D on security. One caveat on the cost column: Codex doesn't report exact spend after a run, so its numbers are estimated from token usage.

Where the security bugs actually came from

Claude and Codex agreed on the security grade for every backend. They sometimes disagreed on severity for a given finding, which is what drove most of the human adjudication.

Firebase produced the worst results, and the pattern was the same across both agents: security rules that looked reasonable but had a gap. Take one high-severity example. If the original creator of an organization is removed, the rule that's supposed to stop them from acting as admin only checks whether their UID matches the stored creator ID on the org document. Nothing in the rule re-checks current membership, so a removed creator can write themselves back in as admin. Codex made an equivalent mistake in its own Firebase run. Roughly, the flawed pattern looked like this:

1match /organizations/{orgId} {
2  allow update: if request.auth.uid == resource.data.creatorId;
3}
4

The rule checks identity, not current membership, so removing a user from an organization doesn't actually revoke what that rule allows them to do. Both models wrote a version of this. Writing a security rule that's correct under every mutation path is a narrow, unforgiving kind of correctness. Both agents struggled with it in the same way.

Supabase scored a C. There were no high-severity findings, but both agents produced a cluster of medium-severity ones, mostly row-level security policies that checked identity without checking scope. In one case, a comment-update policy verified that the caller was the comment's author, but never checked that the comment's task actually belonged to an organization the caller was a member of. Anyone who knew another task's ID, including one in a completely different organization, could move a comment onto it. Roughly:

1create policy "authors can edit their comments"
2on comments for update
3using (auth.uid() = author_id);
4

That's true but insufficient, because author_id says who wrote the comment, not whether the comment's parent task is still in scope for that user. Codex made a related mistake elsewhere in the same app. A Postgres function marked security-definer inserted activity-log events using a caller-supplied target ID, which let a signed-in user inject fake activity events into organizations they weren't a member of. Both are classic row-level security failure modes. It's the kind of thing Convex's own take on RLS argues you can avoid by keeping authorization logic in server functions instead of policies attached to the table.

Convex was the only backend without a high-severity finding. It still landed on a B rather than an A, because both agents made the same mistake around file uploads. Neither checked what was uploaded. Both served attachments by pulling the file down as raw bytes and turning it into a blob URL that opens directly in the browser. Roughly:

1export const getAttachmentUrl = query({
2  args: { storageId: v.id("_storage") },
3  handler: async (ctx, { storageId }) => {
4    return await ctx.storage.getUrl(storageId);
5  },
6});
7

That URL, opened directly, renders whatever the file contains in the same origin as the app, including an HTML document with embedded JavaScript. An attacker inside the same organization could upload one and get someone else to open it. That's a real, if narrow, path to data theft. I initially thought this was a stretch, but Codex's evaluator made the case that it was enough to knock the grade from an A to a B, and I came around. Convex's file-serving docs note that a stored file can be served as a URL directly from storage.getUrl. The fix keeps that API. You just set the response to force a download, so the browser saves the file instead of rendering arbitrary uploaded content inline.

Code quality: modularity versus speed

For every backend, Claude Code's output graded one letter higher than Codex's. The difference wasn't subtle once you looked at the file tree. Claude split logic into smaller, purpose-specific files. Codex tended to dump more into fewer files, and in the Convex builds it piled components into app.tsx rather than breaking them out. Both agents did some unnecessary data reloading in their Supabase builds, but Codex did more of it. That tracked with its tendency to lump related logic together instead of separating concerns. Codex also took more shortcuts with typing. Claude was more consistent about avoiding any and keeping types flowing end to end from schema to component.

Time and cost

I reran all six build configurations two more times each, 18 runs total, purely to measure timing and cost without evaluation or repairs. Codex was consistently faster, averaging roughly 11 to 13 minutes per build against Claude Code's 23 to 32 minutes. Total average cost across all three backends came out close: Claude Code at about $19.53 and Codex at about $15.89. The backend mattered more for Claude Code's timing than for Codex's. Codex took roughly the same amount of time regardless of backend. Claude Code was noticeably faster on Convex than on Supabase or Firebase.

Why I couldn't fully automate this

I spent about two weeks trying to make the whole pipeline, including functional evaluation, run unattended so I could kick it off overnight and wake up to scored results. I couldn't get there. The functional evaluator used Playwright to simulate clicks and reads, and it was flaky from run to run. I came to believe that's because simulated UI interaction adds its own non-determinism on top of whatever the generated app is doing. I ended up doing that stage by hand instead.

The manual pass caught things the automated evaluator missed entirely. For example: a full page refresh where the app should have updated reactively, or a download that technically worked but felt janky in a way a script wouldn't flag. Automated security and code-quality review worked fine, because those are closer to static analysis. Automated behavioral testing of a UI that a different model just generated from scratch turned out to be much harder than I expected going in.

Conclusion: which backend is best for AI agents

Across this Claude Code vs. Codex comparison, the tradeoff was consistent. Claude Code produced more maintainable code: fewer typing shortcuts, better file separation, at the cost of taking noticeably longer to generate it. Codex was two to three times faster and priced close to Claude Code overall, but its code consistently graded a letter lower on quality.

On security, the two agents ended up roughly on par. One of the more interesting findings here was that the backend mattered more than the agent. Both agents found Firebase security rules and Supabase row-level security genuinely hard to get right, and both made structurally similar mistakes independently. Neither agent had that problem with Convex.

Subjectively, based on this one project, Convex came out ahead as a backend for AI agents to build against, and not only because of the security results. Both agents wrote more reactive front-end code against Convex than against Supabase or Firebase. On those two, the generated code was more likely to lean on callbacks, refs, and effects to keep the UI in sync, which is exactly the kind of code that hides subtle bugs. Neither Supabase nor Firebase generation consistently used atomic backend transactions either, which opens the door to data corruption or performance problems under concurrent writes. Convex sidesteps both issues by default. Transactions aren't something you have to remember to reach for, and reactive queries push updates to the client automatically, which made the resulting front-end code easier for both the agents and me to read.

If you want to check any of this yourself, the full benchmark repo has every prompt, container config, scorecard, and adjudication used to produce these results.

All gas, no breakages

Convex is the reactive backend platform that keeps up with you and your agents. Database, functions, workflow, sync, search, file storage, and more. All TypeScript, zero glue.

Get started