From the Debugger to the Feedback Loop: How I Stopped Stepping Through Code
It was almost midnight and I was three stack frames deep. A checkout total was coming out wrong — sometimes. I had xdebug attached, a breakpoint on the line that summed the cart, and I was stepping through, one frame at a time, watching $total mutate. Step into. Step over. Read the variable. Step again. Reproduce, inspect, guess, tweak, repeat.
I found the bug eventually. An expired coupon was still being applied because of an early return two functions up the stack. I fixed it in one line.
But it had taken me two hours of being the debugger to find that one line. And here’s what took me embarrassingly long to notice: xdebug wasn’t the thing doing the work. I was. I was the loop. The tool just let me run it a little faster by hand.
What a debugger actually is
Strip away the IDE panels and the breakpoint dots and a step debugger — xdebug for PHP, gdb for C, pdb for Python, the DevTools debugger for JavaScript — is a machine for running one loop very slowly:
- Reproduce the failure.
- Freeze execution at a suspicious line.
- Inspect the state — variables, the call stack, what’s true right now.
- Form a hypothesis about what’s wrong.
- Change something.
- Run it again and see if the behavior changed.
That’s the whole loop. Every bug you’ve ever fixed, you fixed by running some version of it. The debugger is just the fastest hand-tool ever built for step 3 — inspect the state — and it makes you do steps 1, 4, 5, and 6 yourself, over and over, holding the entire mental model of the system in your head the whole time.
For most of my career that felt like the job. Debugging was stepping through code. I got good at it the way you get good at anything you do for ten thousand hours. I never questioned whether the loop itself could be handed to something else.
The two halves the agent was missing
When I first used an AI coding tool, I used it the way most people do: as a very fast autocomplete. It could write a fix. But it couldn’t tell whether the fix worked, because it couldn’t do the two things that close the loop — it couldn’t run the code and it couldn’t see the result.
So it guessed. And I went back to xdebug to check its guesses, which was slower than just doing it myself.
The unlock wasn’t a smarter model. It was giving the agent the missing two halves:
- A way to run the thing — the test command, a script that hits the endpoint, a one-liner that exercises the broken path with a real fixture.
- A way to observe the result — the test output, the log file, the response body, the diff between what it expected and what it got.
Give Claude Code those two things and something changes shape. It doesn’t guess anymore. It runs the loop. It reproduces the failure, reads the actual output, forms a hypothesis, edits the code, runs it again, and reads the new output — and it keeps going until the behavior is right, without me stepping through a single frame.
I stopped being the loop. I started handing over the loop.
The part that took me two hours to see: it’s a change of altitude
Here’s the thing I actually want you to take away, because it’s bigger than “the AI can debug now.”
When I was in xdebug, I was operating at the altitude of lines and variables. What is $total on line 214? What’s in $cart at this frame? Which branch did we take? That altitude is precise, and it is exhausting, because to find one wrong line you have to hold a hundred right ones in your head.
The test-and-feedback loop operates at the altitude of behavior. Not “what is $total right now” but “does an expired coupon still discount the cart?” You describe the outcome you want and you watch whether it happens. You stop reading the machine’s internal state and start reading its observable behavior.
| Debugger altitude | Feedback-loop altitude | |
|---|---|---|
| The question you ask | What is $total on line 214? | Does checkout charge the right amount? |
| What you watch | Variables, stack frames, branches | Inputs and outputs, pass/fail, the log |
| Who runs the loop | You, by hand, one step at a time | The agent, on its own, until it’s green |
| What you hold in your head | The whole call stack | The behavior you wanted |
| Where your attention goes | Down, into the internals | Up, toward the customer |
That last row is the one that matters. Every hour I spent at the debugger altitude was an hour I wasn’t spending on whether the checkout flow was any good to use. The step debugger didn’t just cost me time — it pulled my attention down into the machine and kept it there.
You don’t hand it a debugger. You hand it a surface.
This is the skill that replaces “knowing your way around xdebug,” and it’s worth saying plainly, because it’s what actually makes the difference between an agent that fumbles and one that fixes the bug on the first try.
You don’t give the agent a debugger. You give it an observable surface — the tighter and faster, the better:
- A test that reproduces the bug, so the loop has a clear pass/fail signal.
- A command that runs in seconds, not minutes, so the loop can spin many times.
- Logs it can read, so a failure explains itself instead of just going red.
- A fixture or seed that puts the system in the broken state on demand.
- A clear statement of expected-versus-actual, so “right” is defined before it starts.
Notice that every one of those is behavior-level. None of them require a breakpoint. When I set a bug loose on the agent now, the real work — the part that used to be “attach the debugger and start stepping” — is building that surface. Write the failing test. Make the run fast. Make the failure legible. Then let the loop run.
That’s not a lower-skill job than stepping through frames. It’s a higher one. It’s the same instinct that separates a good test suite from a bad one, pointed at debugging.
When I still reach for the debugger
I’m not going to tell you xdebug is dead, because the grounded truth is that it isn’t. The step debugger still wins in a few real cases, and pretending otherwise would be the hype talking:
- A heisenbug or a race condition, where the failure won’t sit still long enough to write a test around it — you genuinely need to freeze time and look.
- A bug with no reproducible entry point yet, where you have to watch the system breathe before you even know what a test would assert.
- A moment where you don’t understand the system at all, and stepping through it once is how you build the map.
But be honest about how often that is. For me it’s the minority — maybe one bug in ten. The other nine were always the same boring loop: reproduce, observe, hypothesize, change, re-run. That loop was never the interesting part of my job. It just felt like it was, because it was slow and it was mine.
Why this compounds
The reason this is more than a party trick is that the surface you build doesn’t evaporate when the bug is fixed. The failing test stays. The fast run command stays. The readable logs stay. The next bug in that system lands into a loop that’s already built — so it gets fixed faster, and the one after that faster still.
That’s the difference between using AI as a faster autocomplete and using it as leverage that compounds — a distinction I wrote about in From Developer to Product Builder. Stepping through frames faster is autocomplete. Building a loop that debugs itself, and gets sharper every time you use it, bends the curve.
And it buys back the one thing the debugger was quietly stealing: your attention. When you’re not the loop anymore, you’re free to spend your hours at the altitude that actually decides whether the product is any good — the customer’s, not the call stack’s.
That midnight checkout bug? Today I’d write one failing test that charges an expired coupon, hand it to the loop, and go look at whether the checkout page was worth paying for while the fix ran itself. Two hours became two minutes of my attention — and my attention went somewhere that mattered.
That shift — from the person hunched over the debugger to the person building the loops and reading the outcomes — is exactly what we train at Product Builders Camp. Not how to memorize your debugger’s shortcuts. How to build the feedback loops that make the debugging boring, so you can spend your judgment on the thing customers actually pay for.
The debugger taught me to find one wrong line. The feedback loop taught me I’d been looking at the wrong altitude the whole time.