What "ready for real users" actually means when you built your app with AI
Ready doesn't mean perfect. It means nothing leaks, nothing breaks quietly, and nothing locks honest people out. Here's what to check before strangers show up.
The first thing you see is the message. It's 11 PM on launch night. You've been refreshing the signups tab, watching the count tick up. Twelve. Seventeen. Twenty-three. Then a customer writes in:
"Quick question, am I supposed to be able to see other people's notes?"
You weren't. You don't.
Until that message, the app was working. After it, you're not sure what working means.
Most founders describe this moment with the same word: shaky. The thing built with Cursor or Claude Code looked fine in testing. It loaded. It accepted signups. It took payments. And then strangers showed up. Strangers do things you and the AI never thought of together.
"Ready for real users" isn't a single test. It's a small list of quiet failures, the kind that only show up once someone is using the app for real.
The list isn't a checklist of perfection. It's closer to a checklist of not bleeding. Three things:
- Nothing leaks.
- Nothing breaks quietly.
- Nothing locks out a real person.
Most AI-built apps fail at least one of those on launch day. The ones that get away with it are the ones nobody has tried yet.
Here's what each of those looks like.
The keys to your house, taped to the front door
When you build with an AI, it asks you for keys constantly: keys to your database, keys to send email, keys to take payments. The AI uses them to make the app work. The question is where those keys ended up.
Three places they shouldn't be: in the code itself; in the page that customers load in their browser; in your project's history on GitHub. All three happen routinely. The AI doesn't keep track of which keys are safe to share and which ones aren't, because that distinction lives in your head, not in the code.
The check is simple to ask for and easy to miss. Open the live page in a browser, right-click, view source. Search for the word that starts the key. Usually something like sk_ or eyJ. If it shows up there, anyone in the world has it.
Login that's actually doing its job
A login screen is the most common thing in software, and the most commonly half-built. The login form asks for an email and password. The app lets the user in. So far, fine.
The part that often gets skipped is what happens after. Take the page that shows your account info. Does it actually check it's you asking? Or does it check whether somebody is logged in, and then trust the URL to figure out which account to load?
That difference is the difference between a private app and a public one. Most AI-generated login systems get the door right and get the rooms behind the door wrong. The customer asking about other people's notes? That's almost always what happened.
The check: log in as one user, copy the URL of something private, log out, log in as a second user, paste the URL. If you see the first user's data, something is broken.
Data that protects itself
Modern apps store your customers' data in a database. The app reads from it. The question is: when the app asks the database "give me the notes for user 42," what stops it from asking "give me the notes for user 43"?
If you're using a tool like Supabase, the answer is usually a set of rules sitting next to the data that says "this user can only read this data." If you're using a plainer setup, the answer is "your code, every time, without forgetting once."
AI tools tend to write the second version. They write the code that fetches the right data when asked correctly, and the wrong data when asked sneakily. They almost never write the rules-next-to-the-data version unless someone specifically asked for it.
The check is harder than the others. Ask someone to look at how your data is being protected, and to show you what would happen if a logged-in user changed the number in the URL.
Payments that won't quietly fail
The first time a payment fails on a live app, founders usually find out three weeks later, from a customer asking why their account was downgraded.
Payments are an entire category of "things that look like they're working when they're not." The customer's card got charged. The app got told about it. But did the app actually update the customer's account? Did it remember to do it the next month, when the renewal happened?
The connections between your payment provider (Stripe, Lemon Squeezy, whoever) and your app are quiet messages that pass back and forth in the background. AI tools often write the code that receives a payment and forget about the code that handles a failed payment, or a refunded one, or a retried one.
The check: pretend to be a customer, fail a payment on purpose (Stripe has test cards for exactly this), and watch what your app does. If the answer is "nothing visible," that's the problem.
Forms that aren't gullible
A form is a place where strangers type things into your app. They will type things you didn't expect. Empty values. A million characters. Other people's email addresses. Their own email address misspelled in a creative new way. Bits of HTML pasted in by accident.
If your app trusts the form completely, every weird input becomes a small bug. If your app re-checks every input on the server side, most of those bugs go away. AI tools default to the first version. The second is more work, and the AI is mostly thinking about the case where everything goes right.
The check: try to break your own form. Put nothing in. Put garbage in. Put a paragraph in a field meant for a name. Watch what happens. If the app crashes, or stores the garbage, or sends an email anyway, that's something to fix.
Errors that don't say too much
When something goes wrong inside an app, two messages exist: the one the user sees, and the one only the people building the app are supposed to see. AI tools sometimes confuse them.
The user-facing message should say something like "Something went wrong, please try again." The internal one, recorded somewhere only you can read, should say what actually happened. If those two leak into each other, the user gets a message full of file paths and database table names, and now they know more about your app than they should.
One pattern in particular: the login form that says "user not found" when an email isn't registered, but says "wrong password" when it is. Those two different messages tell anyone with a list of emails which ones have accounts. AI tools generate that pattern by default, because it's "helpful." Helpful to the wrong person.
The check: try to log in with an email that doesn't exist, then with one that does but with a wrong password. If the messages are different, something is leaking.
Limits, because strangers include scripts
Without limits, one person with a script can try a thousand passwords on your login page in a minute. Or sign up ten thousand fake accounts before breakfast. Or send your "forgot password" email ten thousand times and run up your email bill.
Limits are the speed bumps that say "wait, slow down." AI tools rarely add them by default. The thinking is: "the user only does this once." But strangers aren't users. Some of them are scripts.
The check: ask whether your login, signup, and password-reset pages have rate limits. If the answer is "I don't know" or "I didn't ask for them," they don't.
Failures you'd never notice
The most expensive failure isn't the one that crashes. It's the one that fails quietly at 3 AM, that the customer never reports because they assumed it was their fault, that you only notice three weeks later when revenue is lower than expected.
A real app has a place where errors go to be seen, like Sentry or a corner of your dashboard. AI tools rarely set this up unless asked. They write the code that catches errors and silently moves on, because that keeps the app from crashing. It also keeps you from finding out anything is wrong.
The check: cause an error on purpose. Then go look for it. If you can't find it without reading the code, you wouldn't have found it once real users were on the app either.
The thread underneath
The quiet thread through every one of these is the same. The AI you built with is honest about the code in front of it. It can't be honest about the code that isn't there yet — because nothing told it to be.
That's the gap. The AI builds what you describe. It doesn't build what you forgot to describe. And on launch day, the things you forgot to describe are the things strangers will find first.
None of this needs to be fixed alone. Most of it can be checked quickly with someone who's looked at a hundred apps like yours. We do this every week on live calls. You share your screen, we walk through the list together, and you leave with a clearer view of what's actually broken and what only looked broken.
The list, in one place
Your app works. The real question is what it does the first time it doesn't.