Engineering

    How to Fix Bugs in Vibe Coded Apps

    Fix a vibe coded app by reproducing the bug, pasting the real error, and changing one thing. If two fixes create new errors, roll back instead of prompting again.

    · 12 min read · Tovosolutions Team

    How to Fix Bugs in Vibe Coded Apps

    Fix a vibe coded app by making the bug happen again, copying the real error, and asking the AI to change one thing. Then run the same steps. If the second fix creates a different error, roll back. Another "fix this" prompt will not find the cause.

    The demo usually works on your data. It breaks on a second user, a real card, or a refresh. The model then patches the symptom, the next prompt patches the patch, and the project gets harder to explain than the original bug.

    The steps below are the same whether the app came from Lovable, Bolt, Cursor, v0, or Replit. After the loop, there is a fix for the bugs these apps hit most often, and a clear point where prompting should stop.

    Fix it in this order

    Reproduce the failure, hand the AI the evidence, accept one small change, and rerun the original steps. Skip the reproduce step and every later prompt is a guess.

    1. Trigger the bug on purpose. Same page, same account, same click. If it only fails sometimes, write that down and do not let the model edit yet.
    2. Copy the error, not your summary of it. The red line in the browser console, or the failed request and its status code. "It's broken" is not a bug report.
    3. Name the screen, the user, and the expected result. A member cancelling a booking is a different bug from an admin deleting one.
    4. Ask for the cause before the edit once two fixes have already missed. One change. No cleanup of nearby files.
    5. Rerun step 1. The original case has to pass, and the screen next to it has to still work. If the new error is unrelated, undo the change.

    Lovable's debugging docs say the same thing in their own UI: try the automatic fix once or twice, then describe the bug, then switch to Plan mode before you keep retrying.

    The prompt that gets a real fix

    Paste a report. Do not paste your frustration, and do not paste the last twenty messages.

    Don't change code yet.

    Page: Checkout, logged in as ada@example.com Steps: I applied code SAVE10 to a $50 cart. Expected: total becomes $45. Actual: total stays $50 and the button spins until I refresh. Console: TypeError: Cannot read properties of undefined (reading 'code') at CouponForm.tsx:28 Network: POST /api/coupons/apply returned 500. Response: [paste the body]

    Say which file is wrong and why. Then make one change. Do not refactor anything else.

    If you already have a test runner, add one line to that prompt: "Write a test that fails on this case. Do not change the test to make it pass." A green test the model rewrote is not a fix. Zest's debugging note is right about that part: review the diff, because a passing check can hide a weakened assertion.

    Find the error before you describe it

    You need three panels in the browser, not a tour of developer tools. In Chrome, Edge, or Firefox, press F12 or right-click the page and choose Inspect. Safari hides this until you enable the Develop menu.

    Console. Click the broken button and read the first red line. Later red lines are often fallout from the first one. Copy the file name and line number with the message.

    Network. Repeat the click with this tab open. Red rows, or any status of 400 or higher, are the interesting ones. Open the row and read the response. "POST /api/coupons/apply returned 500" is a better sentence than "checkout is down." Turn on Preserve log if the bug includes a redirect, or the request disappears when the page reloads. Chrome's own DevTools overview covers where these tabs live if the labels in your browser differ.

    The server log. A 500 in the browser is a summary. The real message is where the app runs: the tool's log panel, or the terminal if you started it locally. Ask the model to read that log and quote the line. Do not accept a fix that never mentions it.

    Layout bugs are the exception. Nothing is red. The footer is 2,000 pixels wide on a phone and the page scrolls sideways. Say that measurement. The Elements panel can show it if you hover the node. You do not need to edit CSS there.

    Fixes for the bugs vibe coded apps actually hit

    Match the symptom first. The layer tells you where to look, which saves the hour people spend prompting the wrong file.

    What you see Where it lives What to change
    Click, and nothing happens Browser console The first red error, in that file
    Save succeeds, the list stays old The screen's state Refetch or update the list after the save returns
    You can open another user's record The API query Filter by the logged-in user on the server
    Login works, the next call is 401 Token or cookie Send the session the server actually checks
    Status 500 Server log The exception in the log, not the toast on screen
    Works locally, fails after publish Environment variables The missing key in production
    A payment is recorded twice Webhook handler Ignore an event id you have already stored

    A click does nothing

    The handler is throwing before it updates the page. Copy the console line into the prompt and name the component. Ask for a null check only if the log shows a value is missing. A blanket "make this clickable" edit often wraps the function in another try/catch and hides the next failure.

    Expected result: the same click either completes the action or shows a specific message, and the console stays quiet.

    The list is stale after a save

    The write worked. The screen is still holding the old array. This shows up as a form that "didn't save" even though a refresh proves it did.

    Tell the model the save returned 200 and the list did not change until reload. The fix is to update that list from the response, or to refetch it. It is not a second copy of the form.

    One user can open another user's record

    Login is not permission. A route that loads /invoices/123 and returns whatever row has that id will show the next customer's invoice to anyone who can guess the id. The demo hides this because you only ever log in as yourself.

    The query has to require both the id and the current user:

    // Any signed-in user can read any invoice.
    const invoice = await db.invoice.findUnique({ where: { id } });
    
    // The row has to belong to this session.
    const invoice = await db.invoice.findFirst({
      where: { id, userId: session.user.id },
    });
    if (!invoice) return res.status(404).json({ error: "Not found" });
    

    Return 404 when the row is missing or owned by someone else. A 403 confirms the id exists.

    If the database is Supabase, turn on row level security and add a policy that compares the owner column to auth.uid(). A table in an exposed schema without that policy can be readable with the public key. Supabase documents the setup in their row level security guide. Write the policy for insert and update as well as select. A select-only policy still lets a client overwrite another row.

    Check this before you call the app fixed. It will not throw in the console.

    Login works, then the next request fails

    The sign-in screen stored a token the API never reads, or the cookie is not sent on the next call. Open the failing request in the Network tab and look at the request headers. If the cookie or Authorization header is absent, that is the bug. Say so in the prompt: "The POST to /api/orders has no session cookie. The login response set a token in localStorage. Use one place."

    Also check the production URL. A cookie set for localhost will not show up on the deployed domain, which is why login "works on my machine" and fails the moment you share the link.

    The server returns 500

    Stop reading the toast. Open the log and paste the exception. Common causes in generated apps:

    • A column the code expects is not in the production database. The migration ran locally and never ran on the host.
    • The JSON shape from a third party does not match the sample the model invented. Compare one real response from that provider's dashboard.
    • An environment variable is empty in production, so a client is constructed with undefined and crashes on the first call.

    Ask for a migration or a missing env check. Do not ask it to "handle errors" by swallowing them. A catch block that returns { ok: true } will make the bug invisible and leave the bad data in place.

    The same payment or webhook runs twice

    Providers retry. A generated handler that creates an order every time the webhook arrives will create two orders when the first response was slow. Store the event id and return success immediately when you have seen it before. Confirm with two identical deliveries, not with one happy-path click.

    If the bug is a real charge on a live key, stop prompting and get a person. A wrong payment patch is more expensive than a review.

    It works locally and fails after you publish

    Diff the environment, not the feature. List every key the code reads. Confirm each one exists on the host, points at the production project, and is not the example value from a tutorial (supersecretjwt, password123, a key copied into a client file). Secrets in the browser bundle are public. Move them behind a server route.

    While you are in the dependency list, confirm every package exists on npm or PyPI. Models invent package names. If install fails with a 404, search the registry yourself. Do not accept a similar name from the next message.

    When the AI starts making new bugs

    Two misses in a row means the model is defending its last guess. More error paste in the same chat makes that worse. The context is now full of failed theories.

    Do this instead:

    1. Restore the last version that worked. In Lovable that is version history, or editing the old message and choosing revert. In Cursor, reject the diff. If you use git, go back to the last good commit. Do not keep the broken patches "in case something in them was useful."
    2. Start a new chat. Paste only the original report, the log line, and one sentence about what the bad fixes tried.
    3. Ask it to explain the cause and name the file. Read that explanation. If it does not match the error you copied, correct it before you allow an edit.

    A fresh conversation with the evidence beats a long thread that has already been wrong twice. CodeWithPurpose's debugging guide puts it plainly: one printed value ends more of these loops than another prompt.

    Prove the fix, then check the neighbor

    "The model says it is fixed" is not a test. Run the original steps as the same user. Then run the closest cousin of that bug:

    • The coupon that should fail (expired, wrong code, empty cart), not only SAVE10.
    • A second account that must not see the first account's invoice.
    • The webhook delivered twice.
    • The flow on a phone width if the bug was layout.

    Read the diff the way you would read a teammate's change. Reject it if the patch deletes a check, changes a type to any, or returns success from a catch block. Those are the usual ways a generated fix goes green while the bug stays.

    Security bugs that still look fine in the demo

    A vibe coded app can pass your own click-through and still be open. In 2025, Escape scanned more than 5,600 public apps built this way and reported that about 60% had critical security flaws. The pattern they describe is permissions and exposed secrets, not clever exploits. Their write-up is in the State of Security of Vibe Coded Apps report.

    Before you send the link to a customer, check four things yourself. You can ask the model to list the files. You should read the answers.

    1. Every route that reads or changes a user's data checks the session, and the query includes that user's id.
    2. Admin is a role stored on the server. A hidden button or a isAdmin: true field from the browser is not a role.
    3. No API key, database password, or service-role key is in client code, a screenshot, or the git history you are about to push.
    4. Login and password reset are rate limited. Without that, guessing passwords is free.

    If any of those are wrong and the app already has real users or real payments, treat it as an incident. Roll the exposed key, fix the query, and only then keep building features.

    When a developer should take the app

    Keep prompting when you can reproduce one bug, name the file, and roll back. Hand the repository to a developer when any of these are true:

    • The bug touches payments, login, or another person's data.
    • The same failure is still there after three patches, or each patch breaks a different screen.
    • Nobody on the team can explain what the main files do.
    • You need a production database migration, and a bad one cannot be undone by reverting a prompt.

    That handoff works better with the same report you would have pasted into the model: steps, expected result, actual result, the error text, and the version that last worked. A zip of the project with "it doesn't work" costs a day of archaeology.

    Tovosolutions takes that list and prices the work before writing code. On an existing iPhone or Android app, bug fixes are scoped like a new build: the screens or defects, then a price. A web app that still needs accounts, a backend, and a real deploy is an application build. A focused MVP starts at $4,999, is capped at 8 screens, and is planned for 3 weeks after the scope is agreed. The Scale package starts at $11,999 when you need up to 20 screens and an admin dashboard.

    If the broken part is the model itself, wrong answers or a bill that spikes with every chat, that is a separate scope on the AI app development page. Hosted model usage is billed by that provider, not folded into the build price.

    Those builds include 1 month of help for bugs and setup. Later features sit on the monthly product team, which starts at $4,000. If the thing you actually need is an installable App Store or Play Store app, a Lovable project will not become one by prompting. We covered that limit in Can Lovable make mobile apps?.

    Send the bug list through the contact page. Include the tool you used, whether users are already on it, and which of the four security checks above already failed. Once it is stable, the first 30 days after launch is where the next bugs show up, from crash reports rather than from the demo.

    Questions founders ask while the app is broken

    Can the same AI fix the code it wrote?

    Yes, when you give it a reproduction, the error text, and a single change to make. It is poor at "fix this" with no evidence, and it will agree that a bad patch worked if you do not rerun the original steps.

    Why does each fix create a new bug?

    The model is guessing from the latest symptom and editing more files to support that guess. After two misses, roll back and start a new chat with the original error. Do not stack the failed diffs.

    Can a non-technical founder do this?

    You can do the loop in this article: reproduce, copy the console and network error, paste the four-line report, retest. You should not be the person deciding a payment, auth, or database migration is safe. That is the point to bring in a developer.

    How do I know it is actually fixed?

    The original steps pass, a neighboring case still passes, and the diff does not delete a check or swallow the error. Refresh the page and try a second user before you trust it.

    Should I repair this app or rebuild it?

    Repair it when one flow is broken and the rest is understandable. Rebuild the agreed screens when you cannot explain the code, auth or payments are wrong, or three patches have each broken something else. A short bug list with a price is cheaper than a month of prompts that never land.

    Is a vibe coded app safe to show customers?

    Only after the session check, the ownership check, and the secret check are done on the server. A demo that works for you does not show whether the next account can read your rows. Escape's 2025 scan found critical flaws in about 60% of the public vibe coded apps they reviewed, mostly from permissions and exposed secrets.

    Start your MVP sprint

    Youcouldstillbedebatingthisin3weeksoronboardingyourfirstusers.