How I Debug AI-Generated Code — My Vibe Coder Workflow
It was 2:14am on a Tuesday in February, and the terminal was red. Not a polite red — the kind of red that fills the entire screen, line after line of stack trace, error messages breeding more error messages. I had asked Claude to add a simple feature to Soulin Social — a toggle that let users switch between short-form and long-form output. Ten minutes of conversation, one implementation, and now nothing worked. Not just the toggle. The entire application. The generate button returned a blank screen. The save function threw a 500 error. The login page redirected to itself in an infinite loop.
I pasted the entire error log back to Claude. "Fix this." Claude responded with a patch. I applied it. A different error appeared. I pasted that one. Claude fixed it. A third error. Then a fourth. I was playing whack-a-mole with errors at 2am and each fix introduced a new problem and Claude was confidently wrong in a way that was making me want to throw my laptop into the Landwehr Canal.
I stopped. Closed the laptop. Made tea. Stood at the window and looked at the dark street below my apartment — the Turkish grocery store with its shutters down, the single streetlight reflecting off wet pavement. I breathed. And then I sat back down and did the thing I should have done from the beginning: I read the error message.
Not pasted it. Read it. Actually read the words on the screen, slowly, the way you read a letter from someone who matters.
That night changed my debugging process forever. Not because I learned a technical skill — I still cannot trace code execution in my head. Because I learned a mindset: the error is not the enemy. The error is the map.
The Problem With How Most Vibe Coders Debug
Here is what most vibe coders do when something breaks, because it is what I did for months:
- See red text in terminal
- Panic
- Copy entire error
- Paste to Claude with "fix this"
- Apply whatever Claude says
- Repeat until either it works or you give up
This approach works about 40% of the time. The other 60%, it creates a cascade — each fix addressing a symptom while missing the root cause, introducing new problems that compound until you are further from working code than when you started.
The reason it fails is not that Claude is bad at debugging. Claude is excellent at debugging — when given the right information. The problem is that pasting an entire error log with zero context is like calling a doctor, screaming "IT HURTS," and hanging up. The doctor needs to know where it hurts, when it started, and what you were doing when it began.
My Four-Step Debugging Workflow
After that 2am meltdown, I developed a workflow. It is not elegant. It is not fast on any individual step. But it resolves issues in one or two iterations instead of ten, and it has taken debugging from my most dreaded activity to something I almost — almost — find satisfying.
Here is the overview. Then I will go deep on each step.
- Read the error — actually read it, identify the key line
- Isolate the change — what did I add or modify right before this broke
- Ask Claude with context — the error, the change, and the intent
- Test incrementally — apply the fix, test one thing, confirm, then move on
Step 1: Read the Error Message
This sounds insultingly basic. It is the step that changed everything.
A typical error message in my terminal looks like this:
TypeError: Cannot read properties of undefined (reading 'map')
at generateContent (/app/src/services/content.js:47:23)
at async handleGenerate (/app/src/routes/api.js:112:18)
at async /app/src/middleware/auth.js:28:5
When I was starting out, this was a wall of incomprehensible text. I would copy the whole thing and send it to Claude. Now I read it in three layers:
Layer 1: The error type. TypeError: Cannot read properties of undefined (reading 'map'). This tells me something is undefined that should not be — specifically, something I am trying to use .map() on. Even without understanding code deeply, I can parse that: a list I expected to exist does not exist.
Layer 2: The location. content.js:47:23. File name, line number, character position. I open that file in Cursor and look at line 47. I do not need to understand every line of the file — just what is happening at line 47 and what variable is being .map()-ed.
Layer 3: The call stack. The indented lines show the path the code took to reach the error. It started in auth.js (authentication), went to api.js (the API route), and broke in content.js (the content service). This tells me the general flow and helps me understand which piece of the system is involved.
I do not understand all of this technically. But I understand enough to give Claude a specific question instead of a vague cry for help.
Step 2: Isolate the Change
Before I debug the error, I debug my own actions. What did I change right before this broke?
I keep a simple habit: before asking Claude to implement anything, I commit my working code to git. Not because I understand version control deeply — because it gives me a known-good state to return to if things go wrong.
When an error appears, I run git diff to see exactly what changed since the last working state. Often the problem is obvious from the diff alone — a missing variable, a renamed function, an import that disappeared.
The isolation question is always the same: "Is this error in the new code, or did the new code break something that was already there?" The answer determines everything about how I approach the fix.
If the error is in the new code — for example, line 47 of a file I just modified — the fix is usually straightforward. Something in the new logic is wrong.
If the error is in old code that was working before — something in the new code created a side effect. These are harder. They require understanding the connection between the new change and the old breakage, which is where Claude's ability to trace dependencies becomes essential.
Step 3: Ask Claude With Context
Here is the difference between a bad debugging prompt and a good one.
Bad prompt:
Error: TypeError: Cannot read properties of undefined (reading 'map')
Fix this.
Good prompt:
I added a toggle feature to switch between short-form and long-form output.
After adding it, the generate function broke.
Error: TypeError: Cannot read properties of undefined (reading 'map')
at generateContent (content.js:47)
Here is the relevant code in content.js around line 47:
[paste 10-15 lines around the error]
Here is what I changed:
[paste the git diff or describe the change]
The generate function was working before I added the toggle.
What is causing this and how do I fix it without breaking the toggle?
The second prompt gives Claude five things the first does not: what I was trying to do, when it broke, where it broke, what the surrounding code looks like, and what I want to preserve.
Claude's response to the first prompt is usually a guess. Its response to the second is usually a precise diagnosis.
I have a template I use. It lives in a text file on my desktop. When something breaks, I fill it in:
WHAT I WAS DOING: [describe the feature or change]
WHAT BROKE: [describe the symptom]
ERROR MESSAGE: [paste the key line, not the entire log]
FILE AND LINE: [file name and line number]
RELEVANT CODE: [10-15 lines around the error]
WHAT CHANGED: [git diff or description]
WHAT WAS WORKING BEFORE: [confirm what was fine before the change]
Filling this template takes two minutes. It saves thirty minutes of back-and-forth.
Step 4: Test Incrementally
This is the discipline I had to force myself to develop, because my instinct is the opposite.
When Claude gives me a fix, my old habit was to apply it and immediately test the full application — click every button, try every feature, run the whole flow. If anything was still broken, I would paste the new error and keep going.
Now I test the specific thing that broke. Only that thing. If the generate function threw the error, I test the generate function. If it works, I commit. Then I test one adjacent feature. If it works, I commit. Then the next.
This incremental approach catches cascade failures early. If Claude's fix for the generate function broke the save function, I catch it on the second test — not after I have already moved on and introduced three more changes that make the save function bug impossible to trace.
Soulin members get the full essay library, private group chat, the Soulin OS e-book, and every tool — all for $10/mo. Join Soulin →
Full essay library · Private group chat · Soulin OS e-book · Every tool · $10/mo
The rhythm is: fix, test one thing, commit. Fix, test one thing, commit. Small loops. Each loop ends with a known-good state.
When Nothing Works: The Nuclear Option
Sometimes, despite all four steps, I get stuck. The error does not make sense. Claude's fixes make it worse. I have been going back and forth for an hour and the code is further from working than when I started.
This happens about once a month. When it does, I use the nuclear option: revert to the last working commit and start over.
Not "start over from scratch" — start over from the last known-good state, with a better understanding of what went wrong. I describe the feature I want to Claude again, but this time I include what went wrong in the first attempt: "Last time we tried this, it broke the generate function because [reason]. This time, let us implement it differently."
The nuclear option feels like failure. It is not. It is the fastest path back to working code. The time I "wasted" on the failed attempt is not wasted — it taught me what does not work, which constrains the solution space for the next attempt.
Real Examples From My Build Log
The Infinite Redirect (Soulin Social, January 2026)
Symptom: After adding a new user settings page, the login page redirected to itself in an infinite loop. The browser tab showed "Redirecting..." and never stopped.
My old approach would have been to paste the network tab output and ask Claude to fix it. Instead, I isolated the change — I had added a middleware that checked if the user had completed their profile before allowing access to any page. The middleware redirected incomplete profiles to the settings page. The settings page required authentication. The authentication middleware redirected unauthenticated users to the login page. The login page, after successful auth, redirected to the last requested page — which was the settings page — which triggered the profile check — which redirected to settings — loop.
Once I understood the loop, the fix was one line: exclude the settings page from the profile-completion check.
Time to diagnose with the workflow: twelve minutes. Estimated time with paste-and-pray: unknowable, because the error (infinite redirect) does not produce a clear error message — it just hangs.
The Silent Failure (KINS Sales Agent, March 2026)
Symptom: The chatbot stopped responding to messages. No error in the terminal. No error in the browser console. Just silence.
This was the hardest kind of bug — the kind where nothing is visibly broken. My debugging template forced me to check what had changed: I had updated the Claude API call to use a newer model version. The model change was fine, but the response format had shifted — the new model returned the message in a slightly different JSON structure, and the code that extracted the response text was reading from the old path.
No error because the code did not crash — it just read undefined from the wrong JSON path and displayed nothing. A silent failure that would have taken hours to find without the "isolate the change" step.
The 3am Supabase Meltdown (Soulin LifeOS, February 2026)
Symptom: The dashboard showed "No data" for all metrics, but the database clearly had data when I checked through the Supabase dashboard directly.
The error was an RLS policy I had added to a new table. The policy was correct for the new table but had accidentally been applied to the metrics view as well — and the metrics view used a service role that the new policy did not account for. The dashboard was not broken. It was working exactly as the security policy instructed: showing no data to a role that did not have explicit permission.
I found this by reading the Supabase logs, which showed "permission denied" on the specific query. Without the habit of reading error messages — even when the front end shows no error — I would have rebuilt the dashboard before checking the database.
The Mindset Underneath the Method
The workflow is four steps. The mindset is one sentence: errors are information, not failure.
Every red line in your terminal is the system telling you exactly what went wrong and exactly where. It is not punishing you. It is not telling you that you are not technical enough. It is a map, written in a language you can learn to read — not fluently, but well enough to navigate.
The 2am version of me — the one pasting errors and begging Claude to fix it — was treating debugging as a crisis. The current version of me treats it as a conversation. The system speaks (the error). I listen (read it). I gather context (isolate the change). I respond clearly (the structured prompt). And the system answers (the fix).
This is not a technical skill. It is a communication skill. And communication, unlike coding, is something every builder already knows how to do.
What is the error message you have been ignoring — in your code or in your life — that might be telling you exactly where the problem is?
I write about freedom, healing, and building alone. The full archive is at soulin.co.