AI-Assisted Debugging in Cursor
Bug hunting in Cursor runs in three phases with a different mode for each: Ask mode investigates symptoms and ranks hypotheses, Debug mode instruments the code and collects runtime evidence, and Agent mode applies one targeted fix plus a regression test. The sequence exists to prevent the fix-break-fix loop that guessing produces.
Production returns 500s on 15% of checkout requests. It works flawlessly in development. It started after Tuesday’s deployment, but the diff looks harmless — a small refactor of the payment processing logic — and the error message is the supremely unhelpful “Transaction failed.” Your CEO wants an update every thirty minutes.
The tempting move is to paste the error into Agent mode and say “fix this.” It changes three files, your test case passes, and now a different set of users gets a 403. You tell it to fix that too. Two more files. The original 500 is back, and twenty minutes in you are further from a fix than when you started.
The problem is not Cursor. It is treating debugging as “tell the AI to fix it and hope.” Debugging is a search problem, and AI is very good at searching — as long as you make it search before it edits.
What systematic bug hunting gives you
Section titled “What systematic bug hunting gives you”- A three-phase workflow that uses Ask, Debug, and Agent mode for what each is actually good at
- A hypothesis-generation prompt that turns symptoms into ranked, testable theories
- A Debug mode workflow that instruments code, captures runtime data, and finds root causes from evidence
- A load-test prompt for reproducing an intermittent failure locally
- Copy-paste prompts for type errors, race conditions, and a cascade of build errors
- A post-mortem template that documents the bug, the fix, and the prevention
Debugging as a search problem: the three phases
Section titled “Debugging as a search problem: the three phases”Each phase has a mode, and the mode matters because it constrains what the AI is allowed to do:
- Investigate in Ask mode. Read-only. You are building a model of the failure, and nothing should change while you do.
- Gather evidence in Debug mode (or with manual logging). Instrumentation only, no behavior changes.
- Fix in Agent mode. One targeted change, plus the regression test that proves it.
Skipping straight to phase three is what produces the fix-break-fix loop.
Phase 1: investigate before touching code
Section titled “Phase 1: investigate before touching code”Turning symptoms into ranked hypotheses
Section titled “Turning symptoms into ranked hypotheses”Collect every observable fact first, then ask for theories you can actually test:
Expect it to name things like connection pool exhaustion, a race condition, a timeout misconfiguration, or a missing error handler on one code path. The ranking matters less than the second and third points: a hypothesis you cannot confirm or rule out is a theory that will waste your afternoon.
Mapping the call chain
Section titled “Mapping the call chain”When you have a stack trace instead of a symptom list, ask for the path rather than the theory:
“Without making any changes” is doing real work in that prompt. It is the difference between a mental model and a surprise diff.
Phase 2: gather runtime evidence
Section titled “Phase 2: gather runtime evidence”Debug mode
Section titled “Debug mode”Cursor has a dedicated Debug mode for exactly this. Switch to it with the mode picker, or Cmd+. / Ctrl+. to quick-switch. Unlike Agent mode, it follows a structured investigation:
- It explores the relevant files and generates hypotheses
- It adds instrumentation (log statements) that report to a local debug server
- It asks you to reproduce the bug
- It analyzes the collected runtime data
- It proposes a targeted fix based on evidence rather than guesswork
The checkout endpoint at POST /api/checkout is returning "Transaction failed"for ~15% of requests under load. The error happens in the payment processingstep. I can reproduce it locally using a load testing tool (k6 or similar)that sends 200 concurrent requests.
Find the root cause using instrumentation.Debug mode adds logs at the points that matter — database connection acquisition, payment API calls, transaction boundaries — and asks you to trigger the bug. With the runtime data in hand it can usually pinpoint the issue in minutes.
Manual log-and-analyze
Section titled “Manual log-and-analyze”When Debug mode is unavailable or you want tighter control, do the same thing by hand. Instrument first:
Then reproduce, and bring back two runs rather than one. The comparison is what makes the diagnosis possible:
Reproducing an intermittent failure on purpose
Section titled “Reproducing an intermittent failure on purpose”If you cannot trigger the bug by hand, have Agent mode build something that can:
A load test that reproduces the failure is worth more than the diagnosis it produces, because it is also how you verify the fix later.
Reading the evidence
Section titled “Reading the evidence”@logs/debug-output.log @src/payment/processor.ts
Here are the debug logs from the load test. Analyze them and answer:
1. Which requests failed and what do they have in common?2. What is the timing pattern? Do failures cluster at specific intervals?3. Is there a resource that's being exhausted (connections, file handles, memory)?4. Can you identify the exact line where the failure originates?5. What is the root cause?
Show me the specific code that needs to change and explain why.A common finding in this scenario: the payment processor acquires a database connection and fails to release it on certain error paths. Under load the pool exhausts, and the subsequent timeout gets wrapped in the generic “Transaction failed” message — which is why the error text never pointed at the real problem.
Phase 3: the targeted fix
Section titled “Phase 3: the targeted fix”Fix the cause, and only the cause
Section titled “Fix the cause, and only the cause”Switch to Agent mode only now, and constrain it. The template below is the one to keep:
Filled in for the connection leak above, the same shape becomes concrete — note how much of it is about making the next occurrence diagnosable rather than about the leak itself:
The regression test that proves it
Section titled “The regression test that proves it”Every fix gets a test that would have caught the bug:
Add a regression test for the bug we just fixed.
The test should:1. Set up the exact conditions that caused the failure (user with [specific condition])2. Call the endpoint with the same request that was failing3. Assert that it succeeds with the correct response4. Also test the edge case where [related condition]
Put the test in @src/routes/__tests__/orders.test.ts following existing patterns.Run the test to confirm it passes.If the test fails, the fix was incomplete. If it passes, you have something that stops the bug coming back.
Finding the commit that introduced it
Section titled “Finding the commit that introduced it”When the bug started after a specific deployment and you have a reliable test, let git do the search:
The checkout load test passes on commit abc123 (Monday) but fails on HEAD (Tuesday).There are 12 commits between them. Help me set up a git bisect:
1. Create a script that runs the load test and returns exit code 0 if fewer than 1% of requests fail, exit code 1 otherwise2. Show me the git bisect commands to find the exact commit that introduced the regressionTwelve commits narrow to one in about four iterations.
Recurring patterns worth a saved prompt
Section titled “Recurring patterns worth a saved prompt”Type errors that point at the wrong place
Section titled “Type errors that point at the wrong place”TypeScript reports this error at build time:
[PASTE TSC ERROR]
Trace the types involved. Show me:1. Where the type is defined2. How it flows through the code to this point3. Why the types are incompatible4. The minimal fix that maintains type safety
Do not use `any` or type assertions to fix this. Find the real type issue.Race conditions
Section titled “Race conditions”This code has a race condition -- when two requests arrive simultaneouslyfor the same user, the second request overwrites the first one's data.
Relevant code: @src/services/user-service.ts
Analyze the concurrent execution paths and identify:1. Which operation is not atomic2. Where the race window exists3. The best fix (optimistic locking, database transaction, or mutex)
Show me the fix with before/after code comparison.A cascade of build errors
Section titled “A cascade of build errors”With many build errors, fixing them one at a time is the slow path — most of them are downstream of one real problem:
With auto-run enabled, the agent runs the build, reads the errors, fixes them, and iterates until it passes — the loop Steve Sewell at Builder.io popularized.
Writing the post-mortem
Section titled “Writing the post-mortem”The step most teams skip, and the one with the longest payoff. Draft it in Ask mode while the session is still in context:
Based on this debugging session, write a post-mortem document covering:
1. Summary: what broke, who was affected, how long it lasted2. Timeline: when it started, when detected, when fixed3. Root cause: the connection leak in payment processing4. Fix: what changed and why5. Detection gap: why our monitoring didn't catch this sooner6. Prevention: what we will do to prevent similar bugs (connection pool alerting, code review checklist for resource cleanup)7. Action items with owners and deadlines
Save to docs/postmortems/2026-02-checkout-connection-leak.mdWhere AI-assisted debugging goes wrong
Section titled “Where AI-assisted debugging goes wrong”You are stuck in the fix-break-fix loop. Phase 1 got skipped. Go back to Ask mode, build the model of the system, then make exactly one targeted change.
Instrumentation changes the bug’s behavior. Log statements alter timing enough that a race condition disappears — the Heisenbug problem. Use lighter instrumentation: increment atomic counters instead of logging strings, or take process.hrtime() timestamps that need no I/O.
The AI produces a plausible but wrong root cause. Models pattern-match well and can be confidently wrong. Verify before implementing: if the diagnosis is right, the fix should drive the load test’s error rate to near zero.
The fix papers over the symptom. The classic bad fix is a retry wrapper around the failing operation instead of plugging the leak. Retries mask pool exhaustion briefly and make it worse under sustained load. Push back and ask for the cause.
The agent “fixes” the test instead of the code. Say it outright: “The test is correct. The implementation is wrong. Fix the implementation to make the test pass.”
The instrumentation does not capture the issue. The bug is somewhere else in the call chain than you assumed. Widen the logging, or reproduce several times — intermittent behavior needs more than one sample.
Nothing reproduces locally. Some bugs need production data volumes, real network latency, or specific hardware. Instrument production with structured logging and analyze it in Ask mode, and have the agent search for known issues in the library or service that is failing.
The trace leads into node_modules. Use @Docs to check the library’s issue tracker and changelog, then ask directly: “Has this library been reported to have connection leaks or timeout issues in recent versions?”
There are too many errors to triage. Fix compile errors first because they block everything else, then runtime errors, then logic errors, using the build-cascade prompt above.