# AI-Assisted Debugging in Cursor

URL: https://developertoolkit.ai/en/cursor-ide/test/bug-hunting/
Description: Find intermittent failures and race conditions in Cursor: investigate read-only, reproduce with a failing test, fix one cause, and prove it with evidence.

AI-assisted bug hunting in Cursor is a three-phase loop: the agent investigates the failure without editing code, reproduces it with a failing test or load script to collect runtime evidence, and only then makes one targeted fix in Agent mode. The reproduction doubles as the proof, so the fix is accepted on a red-to-green test, not on a plausible-looking diff.

**Documentation freshness**

**Covers:** the Cursor 3.x line; the current release on 2026-10-12 is Cursor 3.24 (2026-10-09, Cursor's release notes). Cursor's help lists four modes: Agent, Ask (read-only), Plan, and Debug (for tricky bugs that need runtime evidence). Shift+Tab cycles through them, and each mode starts a fresh context window (checked 2026-10-12). Plan Mode, Agent mode, Bugbot, Rules, Agent Skills and MCP were first verified against cursor.com on 28 August 2026. The steps below use Plan Mode and Agent mode; where the table names Ask or Debug mode, you can use that mode instead.

This page is for developers who debug with Cursor's agent and for tech leads who decide what evidence a bug-fix pull request must carry. The situation it solves: production returns 500 on about one in seven checkout requests since Tuesday's deploy, the diff is a small refactor of payment code, and the only error text is `Transaction failed`. You paste the error into Agent mode and ask it to fix it. It edits three files, a different group of users now gets 403, and twenty minutes later the original 500 is back.

The agent did not fail at coding. It was asked to edit before anyone knew the cause. Debugging is a search problem, and the agent searches well once you stop it from editing during the search.

## What you'll walk away with from this bug-hunting workflow

- A mode-per-phase plan that keeps the agent read-only until the root cause is proven
- Eight copy-paste prompts: symptom triage, reproduction, instrumentation, log comparison, the constrained fix, a regression test that must fail first, the build-error cascade, and race-condition diagnosis
- A `git bisect run` recipe for regressions that started with a specific deploy
- An evidence checklist a reviewer can approve without reading every line of the fix
- Recovery steps for the six ways AI-assisted debugging goes wrong

## Which Cursor mode fits each debugging phase?

Each phase limits what the agent may change. The limit, not the model, is what stops the fix-break-fix loop.

| Phase | Goal | Cursor surface | The agent may change | Leave the phase when |
| --- | --- | --- | --- | --- |
| 1. Investigate | Ranked, testable hypotheses | Plan Mode (or Ask mode) | Nothing | Every hypothesis names the observation that would rule it out |
| 2. Reproduce and gather evidence | A failure you can trigger on demand | Agent mode, restricted to tests, scripts and logging (or Debug mode) | New test files, load scripts, tagged log lines | A test or script fails for the reason you predicted |
| 3. Fix and prove | One cause removed | Agent mode | Only the files the diagnosis names | The reproduction passes, the suite is green, and the debug logging is gone |

[Plan Mode](/en/cursor-ide/build/agent-modes-deep-dive/) "creates detailed implementation plans before writing any code", which makes it a safe place to think. Before you start, commit or stash your work so that `git diff` shows only what the agent changes during the hunt. For a rollback point inside one session, see [checkpoints and branching](/en/cursor-ide/build/checkpoints-branching/).

## Phase 1: investigate the bug without touching code

### Turn symptoms into ranked hypotheses

Write down every observable fact first: the error text, the rate, the environments where it does and does not happen, the start time, and what changed. Then ask for hypotheses you can test, not for a fix.

**Copy-paste prompt: symptom triage (Plan Mode or Ask mode)**

```text
Do not edit any files. I want a diagnosis plan, not a fix.

Symptoms:
- POST /api/checkout returns 500 with the body "Transaction failed"
- About 15% of requests fail; the rest succeed with the same payload shape
- Production only; I cannot reproduce it on my laptop with single requests
- Started after Tuesday's 14:30 UTC deploy (a refactor of src/payment/processor.ts)
- Failures cluster during traffic peaks, but not exclusively
- The real exception is caught and rewrapped, so there is no stack trace

Read @src/checkout, @src/payment and @src/middleware.

List the five most likely root causes, ranked. For each one give:
1. The code location that makes it possible (file and function)
2. The evidence that supports it
3. The single observation that would rule it out
4. The cheapest way to make that observation (a log line, a metric, a test)
```

Expect answers such as connection-pool exhaustion, a race on shared state, a timeout mismatch, or an error path that skips cleanup. Point 3 matters most. A hypothesis that no observation can rule out consumes an afternoon and produces nothing.

### Pull production evidence into the session with an MCP server

When the failure lives in production, give the agent the real events instead of your summary of them. The official Sentry MCP server (`getsentry/sentry-mcp`, stdio package `@sentry/mcp-server` 0.42.0, checked with `npm view` on 26 September 2026) exposes issues, events and traces. Its remote endpoint is `https://mcp.sentry.dev/mcp`, and Cursor declares a remote server by URL in the project file `.cursor/mcp.json`:

```json
{ "mcpServers": { "sentry": { "url": "https://mcp.sentry.dev/mcp" } } }
```

The remote server signs you in with OAuth, so no token goes into the file. The first connection opens a browser sign-in, so add the server and sign in before an incident, not in the middle of one. Every loaded server also adds its tool definitions to the agent's context: [reducing MCP token cost](/en/ecosystem/mcp/reducing-mcp-token-cost/) shows how to keep that small. Then ask in the same read-only mode: "Find the most frequent unresolved error on the checkout endpoint in the last 24 hours, open its trace, and map each frame to a file in this repository. Do not edit anything." The before-and-after difference is that the agent reads the stack trace your handler swallowed, instead of guessing from the wrapped message. Other observability servers are compared in [observability MCP servers](/en/ecosystem/mcp/observability-mcp/).

## Phase 2: reproduce the failure and collect runtime evidence

### Reproduce before you diagnose

A reproduction is worth more than a diagnosis, because it is also the test that proves the fix later. For an intermittent failure under load, have Agent mode write the script and nothing else.

**Copy-paste prompt: reproduce an intermittent failure under load**

```text
Only create new files. Do not change any existing source file.

@src/checkout/route.ts @src/payment/processor.ts

Write a k6 script at tests/load/checkout-stress.js that:
1. Runs the real checkout flow against http://localhost:3000: create cart, add two items, POST /api/checkout
2. Ramps from 10 to 200 virtual users over 60 seconds, then holds for 30 seconds
3. Varies the payment amount and card type on each iteration
4. Counts responses by status code and counts bodies containing "Transaction failed"
5. Sets a threshold so the run fails when more than 1% of checkouts fail

Then tell me the exact command to run it and what output would confirm the bug.
```

Run it with `k6 run tests/load/checkout-stress.js`. If the failure rate climbs with virtual users, you have a reproduction. If it stays at zero, the difference between your laptop and production (data volume, pool size, network latency) is itself the next hypothesis to test.

For a deterministic bug, the reproduction is a unit or integration test that fails today. The [test-driven workflow in Cursor](/en/cursor-ide/test/testing-tdd/) covers writing that test first.

### Instrument the path, not the behaviour

Cursor's Debug mode is for bugs that need runtime evidence (Cursor's help, checked 2026-10-12), which is what this step collects, so you can use it here. The manual version below collects runtime evidence in any mode and keeps the instrumentation easy to remove.

**Copy-paste prompt: diagnostic logging only**

```text
Add diagnostic logging to the checkout flow to test these hypotheses: [PASTE THE TWO TOP HYPOTHESES FROM PHASE 1].

Log, using our logger in @src/lib/logger.ts at level 'debug':
1. Request received: request ID and user ID (never card data)
2. Database connection acquired and released: pool size, active and waiting counts
3. Before and after the payment gateway call: duration in milliseconds and outcome
4. Every early return and every catch block: the original error class and message before it is rewrapped

Prefix every line with [CHECKOUT_DEBUG] so it can be found and removed with one grep.
Do NOT change any business logic, control flow or error handling. Only add log statements.
When done, show me `git diff --stat` so I can confirm only logging changed.
```

Then run the reproduction twice and keep one failing and one passing trace. The comparison is what makes the diagnosis possible.

**Copy-paste prompt: compare a failing and a passing trace**

```text
Do not edit any files.

Here is the [CHECKOUT_DEBUG] output of a FAILED checkout:
[PASTE FAILING LOG]

Here is the output of a SUCCESSFUL checkout from the same load run:
[PASTE PASSING LOG]

1. Where do the two executions first diverge?
2. Which of my hypotheses does this confirm, and which does it rule out?
3. Name the exact function and line that causes the failure, and explain the mechanism.
4. Predict what the load test will show once that cause is removed.
```

Point 4 is your acceptance criterion for phase 3. A typical finding for this scenario: `processPayment` acquires a pooled connection, and the validation-failure branch returns before releasing it. Under load the pool drains, later requests time out waiting for a connection, and the catch block rewraps the timeout as `Transaction failed`. That rewrap is why the error text never pointed at the cause.

### Find the commit that introduced the regression

When the bug started with a specific deploy and you have a reproduction that exits non-zero on failure, let git search the history. The k6 threshold from the load-test prompt already gives you that exit code. The load test needs a fresh app server on every commit, so wrap the steps in a small script, for example `tests/load/bisect.sh`:

```bash
#!/bin/sh
npm ci >/dev/null || exit 125          # install this commit's lockfile, not the last one's
npm run build >/dev/null || exit 125   # a commit that does not build is skipped, not blamed
node dist/server.js & pid=$!           # start the server itself, not an npm wrapper
trap 'kill $pid 2>/dev/null' EXIT      # stop it however the script ends
sleep 5                                # give the server time to listen on localhost:3000
k6 run --quiet tests/load/checkout-stress.js
```

Then let git drive it:

```bash
git bisect start HEAD abc1234        # bad commit first, then the last known good one
git bisect run sh tests/load/bisect.sh
git bisect reset                     # return to where you started
```

Replace `abc1234` with Monday's deployed commit. Git halves the range on every step, so 12 commits take at most four runs. Exit code 125 tells bisect to skip a commit it cannot test, so a broken build never gets blamed for the regression. Replace `dist/server.js` with your build's entry point: killing an `npm start` wrapper can leave the real server running on port 3000, and the next step then tests the wrong commit. Port 3000 must be free before `git bisect run` starts and before each step. Keep `bisect.sh` out of the commits you are testing (an untracked file survives each checkout), and raise the `sleep` if your server starts slowly.

## Phase 3: fix one cause and prove it

### Constrain the fix to the diagnosis

Switch to Agent mode only now. Cursor starts a fresh context window when you switch modes (Cursor's help, checked 2026-10-12), so paste the diagnosis in, and the agent works from evidence rather than from its own new theory.

**Copy-paste prompt: the constrained fix**

```text
Root cause (proven by the [CHECKOUT_DEBUG] traces): in @src/payment/processor.ts, processPayment acquires a pool connection and the validation-failure branch returns without releasing it. Under load the pool drains and later requests time out.

Fix only this:
1. Move input validation before the connection is acquired
2. Wrap all connection use in try/finally so release always runs
3. Keep the original error class when rewrapping, so logs show ConnectionTimeoutError instead of "Transaction failed"

Rules:
- Touch only src/payment/processor.ts and new test files. If another file must change, stop and tell me why first.
- No retries, no larger pool, no longer timeouts. Those hide the leak.
- Do not edit or delete existing tests.
- Remove every line containing [CHECKOUT_DEBUG] when you are done.
```

The "no retries" rule is there because a retry wrapper is a common plausible-but-wrong fix: it lowers the error rate for an hour and drains the pool faster under sustained load.

### Write a regression test that fails first

A regression test only proves the fix if it fails on the unfixed code. Ask for that proof explicitly.

**Copy-paste prompt: regression test with a red-then-green check**

```text
Write tests/payment/connection-leak.test.ts following the patterns in the existing payment tests.

It must:
1. Call processPayment with input that fails validation, 50 times in a row
2. Assert that the pool's active connection count returns to its starting value
3. Assert that a valid payment still succeeds afterwards

Prove the test is real:
- Run `git stash push src/payment/processor.ts`, run the new test, and show me that it FAILS
- Run `git stash pop`, run it again, and show me that it PASSES
- Then run the full test suite, the type check and the linter, and report each result
```

If the test passes on the unfixed code, it does not test the bug. Delete it and describe the failing condition more precisely. Keep the test from being edited later to fit the code: [protect the oracle](/en/workflows/test/protect-the-oracle/) explains how.

## How do you know the bug fix is right without reading every line?

You accept the fix on evidence, not on how the diff reads. Attach this to the pull request:

| Evidence | How you produce it | What it proves |
| --- | --- | --- |
| Regression test red, then green | The stash run above, output pasted into the PR | The test detects this bug and the fix removes it |
| Reproduction before and after | `k6 run tests/load/checkout-stress.js` on the old and new commit | The failure rate drops to what the phase 2 prediction said |
| Full gates green | Test suite, type check and lint in CI | Nothing else broke |
| Scope matches the diagnosis | `git diff --stat` lists only the named file and new tests | The agent did not refactor on the side |
| Instrumentation gone | `git grep -c CHECKOUT_DEBUG` returns nothing | No debug logging ships |
| Independent review | [Bugbot](/en/cursor-ide/ship/bugbot/) or a second agent reviews the PR against the diagnosis | A reviewer that did not write the fix looked for side effects |

Sign-off: the developer who ran the hunt owns the evidence. The reviewer, typically the tech lead for an incident fix, approves when every row is present, and reads the diff only where a row is missing or surprising. After the deploy, watch the same error in your monitoring for one traffic peak before you close the incident. The [monitoring guide](/en/cursor-ide/operate/monitoring/) covers that alert. The wider practice is described in [reading evidence instead of code](/en/ladder/evidence-not-diffs/).

## How do you debug build-error cascades and race conditions in Cursor?

Two recurring bug shapes need their own recipe: a wall of compiler errors with a few hidden causes, and a lost update that only appears under concurrency.

### A cascade of build or type errors

Forty errors after a dependency bump usually have two or three causes. Fixing them in file order is the slow path.

**Copy-paste prompt: resolve a build-error cascade from the root**

```text
Run `npx tsc --noEmit` and read every error before changing anything.

1. Group the errors by root cause (a changed type, a removed export, a renamed module). Show me the groups.
2. Fix the root cause of the largest group first, then re-run the type check.
3. Repeat until it passes.

Do not use `any`, `as` casts, `@ts-ignore` or `@ts-expect-error` to silence an error. If the correct fix needs a design decision, stop and ask me.
```

### How do you debug a race condition with the agent?

**Copy-paste prompt: diagnose a race condition**

```text
Do not edit any files yet.

When two requests update the same user at the same time, the second write overwrites the first. Relevant code: @src/services/user-service.ts

1. Name the read-modify-write sequence that is not atomic, with file and line
2. Describe the interleaving that loses the update, step by step
3. Compare three fixes for this codebase: a conditional update with a version column, a database transaction with row locking, and an in-process mutex. Say which one still works with several server instances.
4. Write a test that fires 20 concurrent updates and asserts that none is lost. It must fail on the current code.
```

Logging can change timing enough to hide a race. If the bug disappears once instrumented, record timestamps into an in-memory array with `process.hrtime.bigint()` and print them after the run, so the hot path does no I/O.

## How do you write the post-mortem for an AI-assisted fix?

Ask the agent, in Plan Mode or Ask mode, for a post-mortem with summary, timeline, root cause, fix, detection gap, prevention and action items, saved to `docs/postmortems/`. Switching modes starts a fresh context window (Cursor's help, checked 2026-10-12), so attach the diagnosis notes and the fix commits with `@` instead of relying on the earlier session. The detection gap ("why did our monitoring show `Transaction failed` instead of the timeout?") usually produces the most valuable action item. Check the timeline against your incident log before you publish it: the agent knows only what you attached.

## Which recurring bugs deserve a saved prompt?

Save prompts you reuse as a Rule or an Agent Skill so the whole team gets them. The [Rules guide](/en/cursor-ide/quick-start/project-rules/) shows the format. For a ready-made method, the `systematic-debugging` skill from obra/superpowers is written for use "when encountering any bug, test failure, or unexpected behavior, before proposing fixes", and walks the agent through root-cause investigation, pattern analysis and hypothesis testing before implementation. Install it for Cursor with the `skills` CLI (1.7.0, checked 26 September 2026):

```bash
npx skills add obra/superpowers --skill systematic-debugging -a cursor
```

Read the skill's `SKILL.md` before you install it: open issue #2277 (10 September 2026) reports that its directory ships pressure-test fixtures that get copied into the runtime folder. More options are compared in [testing and debugging skills](/en/ecosystem/skills/testing-debugging-skills/).

## When AI-assisted debugging in Cursor goes wrong

**The fix-break-fix loop.** The agent edits, something else breaks, and it edits again. Recovery: `git stash` or restore the last clean commit, return to Plan Mode, and do not leave phase 1 until each hypothesis has a falsifying observation.

**A confident but wrong root cause.** The explanation reads well and the fix changes nothing. Recovery: the phase 2 prediction exposes it. If the load test does not move the way the diagnosis predicted, the diagnosis is wrong, however convincing it sounded. Go back to the trace comparison with the new data.

**The fix hides the symptom.** Retries, a larger pool or a longer timeout make the error rarer without removing its cause. Recovery: reject the diff and restate the rule "fix the cause named in the diagnosis; no retries, pool or timeout changes".

**The agent edits the test instead of the code.** The regression test suddenly passes because its assertion changed. Recovery: say "The test is correct. The implementation is wrong. Do not modify tests/payment/connection-leak.test.ts." Then check `git diff --stat` for test-file changes before you accept anything.

**The instrumentation misses the failure.** The logs show nothing different between the failing and passing runs. Recovery: the bug is elsewhere in the call chain. Widen the logging one layer out (middleware, the database driver, the HTTP client) and collect several failing samples, because one intermittent sample can mislead.

**Nothing reproduces locally.** Some failures need production data volumes, real latency or specific infrastructure. Recovery: add structured, tagged logging behind a flag, deploy it, analyse the production output in Plan Mode or Ask mode, and ask the agent to check the failing library's changelog and issue tracker for known leaks or timeouts in your version.

## Where to go next after fixing the bug

- [Test-driven development in Cursor](/en/cursor-ide/test/testing-tdd/) — Write the failing test first for new work, so fewer bugs reach this page.
- [Testing patterns](/en/cursor-ide/test/testing-patterns/) — Build the unit, integration and end-to-end suite that catches this class of bug.
- [Agent modes deep dive](/en/cursor-ide/build/agent-modes-deep-dive/) — How Plan Mode and Agent mode differ, and when to switch between them.
- [Debugging patterns (cookbook)](/en/cookbook/error-handling-recipes/debugging-patterns/) — Tool-independent recipes for memory leaks, timeouts and flaky failures.
- [Debugging in Claude Code](/en/claude-code/test/debugging/) — The same investigate-reproduce-fix loop from the terminal.
- [Bug hunting in Codex](/en/codex/test/bug-hunting/) — Reproduction-first debugging across the Codex app, CLI and cloud.
