Debug a Stack Trace Faster With AI
Fifty lines of red text. A TypeError: Cannot read properties of undefined (reading 'id'). Somewhere in there is the actual cause, and somewhere else is forty lines of framework internals you don't care about. This is the moment most developers either start bisecting with console.log or paste the whole thing into an AI chat and hope. The second option works a lot better than people expect, but only if you feed it the trace the right way — which is what this post is actually about.
I debug a lot of inherited codebases through Upwork and Fiverr work, which means I'm constantly staring at stack traces from projects I didn't write and don't have full context on. AI-assisted trace reading has cut my "where do I even start" time down more than almost any other workflow change I've made this year.
Why a Raw Stack Trace Wastes Your Time
A stack trace tells you where the exception was thrown, not why. In a Node/Express app with five layers of middleware, an ORM, and a couple of async wrappers, the actual line that broke is often buried under a dozen frames of node_modules noise. You scroll past at processTicksAndRejections, past three Express router frames, past a Sequelize internal, before you even reach a line of your own code.
Most tutorials tell you to just "read the trace top to bottom." I'd skip that advice for anything async — the top frame in a Node trace is frequently the symptom location, not the cause, because the original call site already popped off the stack by the time the rejection surfaces. You need the surrounding context (the request payload, the function that called the broken one, recent related code) alongside the trace, not the trace alone.
What AI Actually Does Well Here
An LLM is good at pattern-matching a trace against the kind of bug it represents, and good at holding several files of context in its head at once without getting tired of scrolling. It is not good at guessing facts you didn't give it. So the trick isn't "paste trace, ask for fix" — it's giving the model the trace plus the two or three things it needs to actually reason about your specific code.
The Prompt That Actually Works
This is the structure I use, almost every time:
Here's a stack trace from a Node.js/Express app:
<paste full trace>
Here's the function where the error originates (app/services/orderService.js):
<paste ~20-40 lines around the throwing line>
Here's how it's called (app/controllers/orderController.js):
<paste the calling code>
What's the most likely root cause, and what's the minimal fix?
Also tell me what I should log next time to catch this earlier.
The last line matters more than people think. Asking "what should I log next time" gets you a debugging improvement even when the model's root-cause guess is wrong — and sometimes it's wrong.
A Real Example: The undefined (reading 'id') Case
Say the trace points to orderService.js:47, inside a .then() after a database call:
// orderService.js
async function getOrderTotal(orderId) {
const order = await Order.findOne({ where: { id: orderId } });
return order.items.reduce((sum, item) => sum + item.price, 0); // line 47
}
Fed just the trace, an AI tool will usually guess "null check missing" — technically true, useless. Fed the trace plus this function, it'll correctly flag that Order.findOne returns null on no match, and that the real question is why orderId didn't match anything upstream — which points you at the controller, not this function:
async function getOrderTotal(orderId) {
const order = await Order.findOne({ where: { id: orderId } });
if (!order) {
throw new NotFoundError(`Order ${orderId} not found`);
}
return order.items.reduce((sum, item) => sum + item.price, 0);
}
That's the fix that actually surfaces the real problem (a bad ID getting this far) instead of just suppressing the crash.
When AI Gets It Wrong
It gets confidently wrong most often on timing bugs — race conditions, a listener registered after the event already fired, a stale closure capturing an old value. Those bugs don't live in the trace at all; the trace just shows where the stale state finally broke something. If the suggested fix is a null check or a try/catch wrapper and the bug is intermittent, that's your signal the model is patching the symptom. I'd push back on the model directly at that point — "this is intermittent, not every call — what changes between a working and failing run?" — rather than accepting the first answer.
Frequently Asked Questions
Does this work for compiled languages like Java or Go too, or just JS/Node? Same principle, different noise. Java traces bury the real frame under proxy/reflection layers; Go traces are cleaner but goroutine stacks need the same "give it the calling context" treatment. The prompt structure doesn't change.
My trace is huge and I don't want to paste 200 lines — do I need all of it?
No. Trim node_modules frames down to the first 2-3 and keep every frame from your own codebase. The model needs the shape of the call chain, not the framework internals.
Key Takeaway
A stack trace plus an AI tool beats a stack trace alone, but only once you stop treating the trace as the whole input. Paste the trace, the throwing function, and the calling code together, and ask what to log next time — that third piece is the one most people skip, and it's the one that actually makes you faster at the next bug too.
0 Comments
No comments yet — be the first to share your thoughts.