Skip to content
← Blog AI August 8, 2026

How I Used Claude to Audit My Own AI Outputs — And Found Three Bugs I'd Already Shipped

10 min read
How I Used Claude to Audit My Own AI Outputs — And Found Three Bugs I'd Already Shipped

I want to tell you about a week in February where I found three bugs in code I'd already deployed. Not in my handwritten code — in code I'd generated with Claude, reviewed with my own eyes, thought looked fine, and pushed to production. Three separate projects. Three separate oversights. All caught eventually, none catastrophically, but each one embarrassing enough that I spent a weekend building a more disciplined process around AI-assisted development.

This post is the post-mortem on that week. What broke, why I missed it, and the specific audit loop I now run before I ship anything AI-generated.

The Setup: Why I Was Moving Fast

By February I'd been leaning on Claude fairly heavily for a few months. Not for architecting systems — I'd already learned that lesson the hard way, as I wrote about in Why Claude Kept Lying to Me About PHP Sessions. But for the grunt work: generating form handlers, writing boilerplate validation logic, scaffolding admin table views, that kind of thing. It was genuinely saving me two or three hours a day on the mechanical parts of building.

The problem is that when something saves you time, you start trusting it. Not consciously — you don't sit down and decide "I'm going to stop reading this carefully." You just get faster, the bar for what counts as a review gets lower, and eventually you're skimming instead of reading.

That's exactly where I was by February.

Bug One: The Off-by-One That Only Fired at Midnight

The first bug was in a small scheduling feature for a client's internal tool. They needed a way to flag records that were "due today" vs "overdue." I'd asked Claude to generate the comparison logic in PHP, given it my database schema, and it produced something that looked perfectly reasonable.

The generated code compared timestamps using strtotime('today') against a stored DATETIME column. In testing — which I did at 11am — it worked perfectly. In production, overnight, records that had a due time of 23:45 were being marked overdue at 00:01 the next day, before the client's team even started work.

The issue was timezone handling. Claude had generated the comparison assuming server time, but my cPanel hosting was set to UTC and the client was in WIB (UTC+7). A seven-hour gap that only materialised at the edge of a day boundary. I had not told Claude about the timezone environment. Claude had not asked. Neither of us had flagged it as a variable worth checking.

Bug Two: File Permissions That Were Too Generous

The second one was nastier in potential if not in actual impact. I'd used Claude to generate a file upload handler — similar territory to what I covered in Bulletproof File Uploads in PHP — but for a slightly different use case involving user-submitted documents on a membership platform.

Claude's generated handler was doing most things right: MIME type checking, extension validation, renamed files with a hash prefix. But when it wrote the mkdir() call to create the upload directory, it used 0777 permissions. On a shared cPanel host, that's a real problem. Other accounts on the same server theoretically have access to world-writable directories. The correct permission on cPanel shared hosting is typically 0755 at most, and the upload directory should be outside the webroot anyway.

Claude didn't know my hosting environment. I didn't tell it. The code ran fine in my local setup and I didn't think to audit the mkdir call specifically because the upload logic itself looked correct.

Bug Three: A Silent Failure in a Return Value

The third bug was the subtlest. I'd generated a small utility function that was supposed to return false on failure and the processed data array on success. Inside the function, one of the conditional branches was returning null instead of false in a specific edge case — an empty input array.

The calling code was checking if ($result === false) with a strict comparison. null is not false in a strict comparison, so the failure branch never fired. Instead, the code continued with a null value and wrote an empty record to the database. Silently. No error, no exception, just a ghost row.

I found this one by accident two weeks after it went live, while debugging an unrelated issue. It had been silently inserting empty records every time a user submitted a form with no attachments.

What I Did Wrong: The Root Cause Across All Three

Looking at all three bugs together, the pattern is obvious: I was reviewing AI output the same way I review my own code — checking for logical correctness in the happy path, not systematically auditing for environmental assumptions, edge cases, and failure modes.

When I write code myself, I have implicit context loaded: I know the server timezone, I know the hosting environment, I know what the calling code expects. When Claude generates code, it's working from whatever context I explicitly provided in the prompt. If I didn't mention timezone, it assumes a sane default. If I didn't describe the hosting environment, it generates generic PHP. If I didn't specify strict return type expectations, it returns whatever seems reasonable.

The gap between "what Claude assumed" and "what my environment actually was" is exactly where all three bugs lived.

The Audit Loop I Built After This

After that week, I built a two-stage audit process for any AI-generated code that's going into production. It sounds more formal than it is — in practice it takes maybe ten to fifteen extra minutes per function, which is nothing compared to debugging a production issue at midnight.

Stage One: The Environment Prompt

Before I even ask Claude to generate anything, I now write what I call an environment brief in my prompt. It covers:

  • Server: shared cPanel hosting, PHP 8.1, timezone set to UTC
  • Database: MySQL 5.7, no ORM, raw PDO with prepared statements
  • File system: uploads must go to a directory outside public_html, permissions 0755 max
  • Return conventions: functions must return false explicitly on failure, never null
  • Strict comparison: all boolean checks use === not ==

This sounds tedious to write every time, so I keep it as a text snippet I paste at the top of any code-generation prompt. Takes three seconds.

Stage Two: The Audit Prompt

Once Claude generates the code, I run a second, separate prompt. I paste the generated code and ask Claude to audit it against a specific checklist. Not "is this good code" — that's too vague and Claude will just confirm it's fine. Instead I ask:

"Review this PHP function for: (1) any assumptions about timezone or locale that may not hold on a UTC server, (2) any file system operations with permissions above 0755, (3) any code path that returns null where false is expected, (4) any database input that isn't passed through a prepared statement, (5) any missing edge case handling for empty arrays or zero values. List issues found or explicitly confirm each point is clean."

This approach — forcing Claude to evaluate against named criteria rather than just asking for a general review — catches things that a vague "check this for bugs" prompt misses. It also makes Claude's confirmation meaningful: if it says "point 3 is clean," I know it actually checked that specific thing.

What This Process Actually Caught

In the two months since I started running this audit loop, it's flagged real issues on four separate occasions:

  • A date() call that used the default server timezone instead of the explicit timezone parameter
  • A prepared statement that had accidentally been refactored into string interpolation at some point during iteration
  • A function that returned an empty string on one failure branch instead of false
  • A recursive function with no depth limit that would have caused a stack overflow on malformed nested input

None of these are catastrophic on their own. But none of them would have been caught by a normal code review either, because AI-generated code tends to look right at a glance — it's syntactically clean, it follows conventions, it just has wrong assumptions baked into it quietly.

The Bigger Lesson: AI Doesn't Know What It Doesn't Know

The thing I keep coming back to from this post-mortem is that Claude is genuinely good at generating code that is correct in the abstract. It knows PHP. It knows PDO. It knows file handling. But it doesn't know my environment unless I tell it, and it won't warn me about environmental assumptions it's making unless I specifically ask.

This is fundamentally different from working with a human developer. A human junior dev working on my codebase would have asked "what's the server timezone?" before writing time-sensitive logic. Claude doesn't ask. It answers. The confidence of the output gives no signal about the completeness of its assumptions.

That asymmetry — confident output, hidden assumptions — is the core thing to understand about using AI for code generation. The more your environment deviates from "a standard VPS running default PHP on localhost," the more aggressively you need to audit.

I've also found that asking Claude to audit its own output works better than asking a second tool, at least for this kind of targeted checklist review. The model has enough context about what it generated to evaluate specific lines intelligently. What doesn't work is asking it to self-audit immediately after generating — the same framing that produced the output tends to produce a confirmation bias in the review. A separate prompt, written from scratch, with explicit criteria, is what actually finds things.

Practical Takeaways

  • Write an environment brief. Paste your hosting environment, PHP version, timezone, permission constraints, and return value conventions into every code-generation prompt. Keep it as a saved snippet.
  • Audit with criteria, not vibes. "Is this code correct?" is a useless audit question. "Does this code make any assumptions about timezone?" is a real one. Make your audit prompt specific.
  • Separate the generation and audit prompts. Don't ask Claude to review immediately after generating. Start a fresh prompt. The cognitive separation matters.
  • The confidence of AI output is not a quality signal. Syntactically clean, logically structured code can still have wrong assumptions. Treat AI output like you'd treat code from a smart developer who's never seen your server before — because that's exactly what it is.
  • Edge cases and boundary conditions are where AI output breaks most often. Test with empty inputs, zero values, and the exact timestamp boundaries your logic touches.

The audit loop adds maybe ten percent to my AI-assisted development time. Given that AI assistance already cut a significant chunk of mechanical work, that's an easy trade. I'm still going fast. I'm just not shipping quiet bugs along with the features anymore.


Frequently Asked Questions

Can you use one AI tool to check the output of another AI tool?

Yes, and it's genuinely useful — different models have different blind spots, so cross-checking Claude's output with Claude itself (using a different prompt framing) or against another model catches errors the first pass missed. The key is giving the auditing prompt full context: the original task, the generated output, and specific things to verify rather than just asking "is this correct?"

What kinds of bugs does AI-generated code most commonly introduce?

In my experience, the most common issues are subtle logic errors in conditional branches, incorrect assumptions about server environment (especially on shared cPanel hosting), and security gaps like missing input sanitisation or improper file permission handling. AI tends to produce code that looks structurally sound but fails on edge cases the model never considered.

How do you decide when AI output is trustworthy enough to ship?

I never ship AI-generated code without at least one deliberate audit pass — either manual review, a second AI prompt specifically asking for critique, or both. The more the code touches authentication, file handling, or database queries, the more scrutiny it gets. Fast-generated UI scaffolding I'll trust more readily; anything touching user data gets treated like it came from a junior developer on their first week.

Share Twitter / X LinkedIn

Enjoyed this? Let's build something.

Start a project →
Keep reading

More articles

Before & After: How I Used AI to Generate Realistic Placeholder Content (And Why My Old Approach Was Embarrassing)
AI August 16, 2026
Before & After: How I Used AI to Generate Realistic Placeholder Content (And Why My Old Approach Was Embarrassing)
Lorem ipsum was killing my client presentations. Here's how I replaced fake filler with AI-generated realistic content — and what changed.
Read →
AI-Assisted Code Review Saved Me Hours — Until It Confidently Broke My App
AI June 16, 2026
AI-Assisted Code Review Saved Me Hours — Until It Confidently Broke My App
AI code review tools are genuinely useful, but blind trust in them is a fast track to subtle, hard-to-diagnose bugs. Here's what I learned from letting Claude audit my PHP codebase — and where it went quietly, confidently wrong.
Read →
Why Claude Kept Lying to Me About PHP Sessions (And How I Finally Got Useful Answers)
AI June 9, 2026
Why Claude Kept Lying to Me About PHP Sessions (And How I Finally Got Useful Answers)
I asked Claude to help me debug a broken session-based login system and it confidently gave me wrong answers three times in a row. Here's the post-mortem on what went wrong — and how I restructured my prompts to actually get reliable help.
Read →