Deep Debugging & Root-Cause
Get to the bottom of a complex bug - fast, on an AI-native desktop app
A repeatable debugging method
After this you can run a disciplined investigation under interview time pressure.
The debugging screen is the heart of this loop. Cursor isn't checking whether you've seen this exact bug - it's checking whether you have a method that gets to the bottom of a bug nobody has seen yet.
A Product Quality Engineer is the senior escalation point between front-line support and engineering. The thing that makes that job possible is a loop you can run on any failure, on an AI-native desktop app whose behavior shifts as models change week to week. Memorized fixes rot. A method doesn't.
Carry one loop into the room and run it out loud. Here it is end to end.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
Two steps are gates: no reliable repro, no trustworthy fix - and naming the root cause, not the symptom, is what ends the loop.
- 1Gather context. Restate the report, pin down what "working" looks like and capture environment: OS, Cursor version, model, plan, repo size. Half of slow debugging is solving the wrong problem quickly.
- 2Reproduce. Get the failure to happen on demand. No reliable repro means you can't tell a fix from a coincidence.
- 3Isolate variables. Narrow the blast radiusHow much breaks if a change goes wrong; the scope of potential damage.. Binary-search the space and flip exactly one thing per test.
- 4Read the evidence. Pull logs, stack traces and a network capture before you theorize. Let the data point first.
- 5Hypothesize. State a specific, falsifiable guess: "indexing never finished, so retrieval returns nothing and the agent answers blind."
- 6Test one change at a time. Run the cheapest experiment that would prove the hypothesis wrong.
- 7Confirm root cause. Name the underlying cause, not the symptom the user typed.
- 8Document. Hand engineering a clean report or leave a note so the next investigation starts where this one ended.
Minimal repro is the deliverablethe step everyone wants to skip
Strip the scenario to the smallest steps that still trigger the bug. A solid minimal repro converts a vague "agent keeps breaking my file" into "on this 3-file repo, with this prompt and Auto model, the agent overwrites unrelated functions 4 runs out of 5." That sentence is most of the diagnosis and it's the artifact an engineer can act on without re-deriving your work.
One variable at a timebisect, don't shotgun
Shotgun debugging - changing five things and seeing if the bug goes away - hides which factor mattered even when it "works." Cut the space in half along whatever axis is cheapest to flip and change only that.
- Axis to bisect
- Version
- How to flip it
- Roll back to a prior Cursor build, rerun
- What it rules in or out
- A regression an update introduced
- Axis to bisect
- Model
- How to flip it
- Swap the selected model, same prompt
- What it rules in or out
- Model-specific quality or a backend regression
- Axis to bisect
- Extensions
- How to flip it
- Disable all, re-enable in halves
- What it rules in or out
- Third-party extension conflict
- Axis to bisect
- Workspace
- How to flip it
- Reproduce in a fresh minimal repo
- What it rules in or out
- Project size, .cursor rules or local config
- Axis to bisect
- Network
- How to flip it
- Try a different network or disable a proxy
- What it rules in or out
- Corporate proxy, firewall or transit issue
| Axis to bisect | How to flip it | What it rules in or out |
|---|---|---|
| Version | Roll back to a prior Cursor build, rerun | A regression an update introduced |
| Model | Swap the selected model, same prompt | Model-specific quality or a backend regression |
| Extensions | Disable all, re-enable in halves | Third-party extension conflict |
| Workspace | Reproduce in a fresh minimal repo | Project size, .cursor rules or local config |
| Network | Try a different network or disable a proxy | Corporate proxy, firewall or transit issue |
Flip exactly one row per test or the result tells you nothing.
Announce your loop in the first 30 seconds: "I'll reproduce first, then isolate one variable at a time, then form a hypothesis I can test." It signals method before you've found anything and buys you patience while you dig. Narrate the dead ends too - a discarded hypothesis is evidence of reasoning, not failure.
Timebox the investigation out loud. Senior quality work isn't infinite digging - it's saying "I've spent 20 minutes, here's what I know, here's what I'd try next and here's the point where I'd escalate." Bottomless rabbit-holing reads as poor judgment, not thoroughness.
Takeaway. Carry one loop - gather, reproduce, isolate, read, hypothesize, test, confirm, document - flip one variable per test and narrate it; the screen grades method over the final answer.
Self-check
QMid-investigation, you've changed the model, disabled two extensions and cleared the index - and the bug disappears. What's the problem with that?
Cursor's stack as a mental model
After this you can reason about where a Cursor bug could live before you start poking.
You can't isolate a bug if you don't know what the layers are. Before touching anything, place the failure on a map of Cursor's stack.
This is the concept layer, so slow down before the drill. Name the mechanism first, then tie it to the role's daily decisions: what changes, what can fail and what proof would make a teammate trust the answer.
Cursor is a VS Code fork running on Electron - a desktop client with extensions and language servers, like the editor you already know. What makes it AI-native is the rest: the client talks to model backends over the network, streams LLM output back into the editor live and feeds those models context retrieved from an embedded index of your codebase. Each of those is a place bugs live and each has different evidence and a different owner.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
A symptom in one layer often originates in another - a UI freeze can be a stuck network call.
The layers, top to bottomwhere a symptom could originate
- Editor / UI (renderer)
- Electron renderer process: panels, Tab UI, ⌘K, chat surface. Freezes, render glitches and JS errors show here.
- Extensions / LSP
- VS Code extensions and language servers. Conflicts, slow servers and feature breakage that survives a Cursor restart but dies with extensions off.
- Local indexing / retrieval
- Embeddings + codebase index that feed @-context and Agent. Stale, partial or empty index = the model answers blind.
- Network transport
- Client-to-backend requests for completions, chat and agent steps. Proxies, timeouts, TLS and rate limits live here.
- Model backend
- The hosted model and serving infra. Latency, errors and quality regressions that hit many users at once.
- Model output itself
- What the LLM produced given a correct prompt: hallucinated edits, oversized diffs, agent loops. Nondeterministic by nature.
A symptom in one layer often originates in another - a UI freeze can be a stuck network call.
The first triage questionclient, server or model?
Before any deep dive, sort the bug into one of three buckets. Each carries different evidence and routes to a different team.
Lives on the user's machine: editor, extension, index, local config.
Evidence: Developer Tools console, output logs, repros on a clean profile.
Often one user or one environment.
Lives in transport or the backend: timeouts, 5xx, rate limits, outages.
Evidence: network capture, status codes, server traces if you're given them.
Usually many users at once, correlated in time.
Prompt and plumbing are fine; the model's output is wrong.
Evidence: the prompt, retrieved context and rates across many runs.
Nondeterministic - judge by frequency, not one bad answer.
One user usually means local config, an extension, the index or that machine's network. A spike across many users at the same moment points at a server-side change, a model regression or a release. Ask "how many, since when?" before you ask "why" - the answer reroutes the whole investigation.
Takeaway. Place every bug on the stack - editor, extension, index, network, backend, model output - then answer one question first: is this client, server or model? Each has different evidence and a different owner.
Self-check
Reading the evidence
After this you can extract signal from logs, traces and captures like an engineer.
Guessing from the symptom is what front-line support does. The escalation point reads the evidence and lets the data narrow the suspect list.
Stack traces: read both endsthrow site vs trigger
Read top-down to find where the error was thrown and bottom-up to find what triggered the call. The frame you care about is the first one in app code - skip the framework and node_modules noise. That line is usually the lead.
TypeError: Cannot read properties of undefined (reading 'range')
at applyEdit (workbench/agent/diffApply.ts:212) <- first app frame: start here
at processAgentStep (workbench/agent/loop.ts:88)
at async streamHandler (node_modules/.../stream.js:40)
at async IncomingMessage.<anonymous> (node:internal/...)Cursor's diagnostic surfaceswhere the client tells you what broke
- Developer Tools console - the Electron renderer's JS errors. Open with the Help menu's Toggle Developer Tools; this catches UI and chat-surface crashes.
- Output / log panels - per-feature logs (the model picker, language servers, extensions). Set the log level to trace when you need detail.
- On-disk log files - the full logs folder via the command palette's Open Logs Folder; this is what you attach to a report.
Capture the networkthe AI-native part of the stack
Most of what makes Cursor Cursor happens over the wire. A HAR capture or the Developer Tools Network tab shows failed and slow model requests, timeouts and payload sizes. Map the status code to a cause fast.
- Signal
- 401 / 403
- Likely cause
- Auth or entitlement - token expired, plan or org policy
- Signal
- 407
- Likely cause
- Corporate proxy authentication required
- Signal
- 429
- Likely cause
- Rate limit - too many requests or quota hit
- Signal
- 5xx
- Likely cause
- Backend error or model-serving incident
- Signal
- Timeout / ECONNRESET
- Likely cause
- Blocked or dropped connection; firewall or transit
- Signal
- TLS / cert error
- Likely cause
- Inspecting proxy (MITM) or local clock skew
- Signal
- 200 but slow
- Likely cause
- Backend latency or a huge prompt payload
| Signal | Likely cause |
|---|---|
| 401 / 403 | Auth or entitlement - token expired, plan or org policy |
| 407 | Corporate proxy authentication required |
| 429 | Rate limit - too many requests or quota hit |
| 5xx | Backend error or model-serving incident |
| Timeout / ECONNRESET | Blocked or dropped connection; firewall or transit |
| TLS / cert error | Inspecting proxy (MITM) or local clock skew |
| 200 but slow | Backend latency or a huge prompt payload |
Status code first narrows client-vs-server before you read a single log line.
A frozen UI is rarely a UI bug. It's often a downstream effect of a stuck network call waiting on a timeout or an agent loop spinning on the same step. Correlate timestamps across the console, the network capture and any server trace you're given - the cause is usually the event just before the symptom, in a different layer.
Debug mode for the cross-layer bugwhen the evidence won't line up
Some bugs sit between layers - a failure that spans services or hides in the seam between logic layers, where standard agent mode's bias for action just guesses and over-edits. Cursor's Debug modeA mode that diagnoses a failure: it reproduces the issue, adds instrumentation and watches the logs, rather than reviewing a pull request. (a distinct mode in the agent selector) is built for exactly that. It instruments the codebase to find the root cause instead of patching the first thing it sees, and it works on anything that can fire a log line, including a compiled executable.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
It validates hypotheses against a real execution path, then fixes only the broken lines and removes its own instrumentation.
Standard agent mode is biased toward action: on a subtle bug it will guess a cause and edit broadly to make the symptom go away. Debug modeA mode that diagnoses a failure: it reproduces the issue, adds instrumentation and watches the logs, rather than reviewing a pull request. trades that speed for a confirmed execution path, so the fix lands on the actual broken lines instead of the nearest plausible ones.
You're relying on the agent to just look at a very specific part of the codebase and only fix what's broken.
Takeaway. Let evidence lead: read stack traces from both ends to the first app frame, map a network status code to client-vs-server and correlate timestamps - the symptom and its cause usually sit in different layers.
Self-check
QA user's Cursor completions silently stop and a HAR capture shows the completions endpoint returning 407. What's the cause and what is it NOT?
Reproducing the un-reproducible
After this you can create reliable repros for intermittent and environment-specific bugs.
The bugs that reach an escalation point are the flaky ones - the ones front-line support couldn't pin down. Reproducibility is the skill that turns those into fixable tickets.
Two kinds of "can't reproduce" need two different attacks. Environment-specific bugs depend on something about the user's setup. Nondeterministic bugs depend on the model's stochastic output. Treat them differently.
For environment-specific bugsvary one dimension at a time
- 1Start from a clean profile. Launch Cursor with a fresh user profile and no extensions to rule out local config. If the bug vanishes, the cause is something the user added.
- 2Reintroduce in halves. Re-enable extensions and settings in groups, bisecting until the bug returns. The last thing you flipped is the trigger.
- 3Sweep the obvious axes. OS, Cursor version, model, file or repo size and network conditions - change one, hold the rest, log the result.
- 4Capture the working and failing states. Diff the two environments; the delta is your repro.
For nondeterministic LLM behaviorrates, not anecdotes
An LLM can give a different answer to the same prompt. One bad output proves nothing - it could be the tail of a distribution. Run the same scenario many times and report the rate. "The agent drops the open file from context in 6 of 20 runs" is a bug an engineer can chase; "it did something weird once" is not.
# Fixed inputs: same repo snapshot, same prompt, pinned model.
# Loop the scenario, classify each run, report the frequency.
for i in $(seq 1 20); do
run_agent --repo ./repro-fixture --prompt "$PROMPT" --model auto \
>> runs/$i.log
done
grep -l "overwrote unrelated function" runs/*.log | wc -l # -> failures / 20When you can, build a small scratch repo or script that recreates the trigger deterministically. A fixed-input fixture is the cleanest gift you can hand engineering, because it removes their setup cost entirely.
Drive the UI with the integrated browserfor the bugs that only show in the app
Some repros are a sequence of clicks, not a script. Cursor's integrated browserCursor's built-in browser, driven by Playwright under the hood, so the agent can navigate, click, screenshot and capture each action's metadata while you watch. has Playwright under the hood, so the agent can drive real browser actions - click a button, navigate a page, take a screenshot - and capture the metadata of each step. While it runs, a banner on the right says the agent is using the browser and a step log builds on the left. Ask it to write each accessed element out to a text file and you have a recorded, replayable trace of the exact path that triggers the bug.
Because the actions are Playwright underneath, the flow you prototype to reproduce a UI bug is one step from being an integration test. Drive the broken path once, capture the steps, then keep them as a regression guard so the bug can't come back silently.
our integrated browserCursor's built-in browser, driven by Playwright under the hood, so the agent can navigate, click, screenshot and capture each action's metadata while you watch. actually has Playwright under the hood.
If you genuinely can't reproduce, that's not a dead end - it's a deliverable. Document exactly what you tried, what you ruled out and the conditions you couldn't recreate. "I ran it 30 times across two OSes and three models without a repro; it only appears on the user's corp network" tells engineering precisely where to look next. Negative results are signal when they're specific.
Don't declare a nondeterministic bug "fixed" off one good run or "present" off one bad one. With stochastic output you need a before-and-after rate. Showing a regression from 30% failure to 2% is convincing; showing one passing run is luck.
Pin the behavior before you touch itguardrail tests for a safe refactor
Once a bug is fixed, the next risk is the fix that quietly changes something else. On a large, idiosyncratic codebase, treat tests as guardrails: identify the current behavior, write tests that pin that exact input/output, then make the minimal change while keeping inputs and outputs identical. This is what makes a big refactor safe - say, migrating a hot path from legacy Java to Go for speed without changing what it returns.
Identify the current state, write tests for that current state, then make the minimal code change - keeping the output of the current state intact.
Repo size isn't the blocker it sounds like. Cursor indexes a brand-new project and a ten-year-old monster the same way: semantic search plus efficient grep inside the agent harness. A repo with tens of thousands of stars, a decade of history and thousands of contributors is still navigable - so "the codebase is too big to test" stops being an excuse.
Takeaway. Split flaky bugs by type: bisect a clean profile for environment-specific ones and measure failure rates over many runs for nondeterministic LLM ones - and when you can't repro, the documented attempt is itself the deliverable.
Self-check
Writing the bug report engineering will act on
After this you can produce a report that turns a vague complaint into a fixable ticket.
The investigation isn't done when you understand the bug. It's done when an engineer can read your report in under a minute and know exactly what to do next.
This is the part of the job leadership sees and the part that scales your impact. A clean report saves engineers their first hour of re-deriving your work and it's the artifact that earns you trust as the escalation point. Lead with impact, not narrative.
The report skeletonin priority order
- Section
- Impact & severity
- What goes here
- One line: what breaks, how bad (data loss / security / outage > broken feature > annoyance) and the reach.
- Section
- Repro steps
- What goes here
- Numbered, exact, copy-pasteable. Include the minimal repo or fixture if you built one.
- Section
- Expected vs actual
- What goes here
- Two short lines. The gap is the bug.
- Section
- Environment
- What goes here
- OS, Cursor version, model, plan, relevant extensions, network shape.
- Section
- Evidence
- What goes here
- Logs, HAR, screen recording, stack trace - attached, not described.
- Section
- Hypothesis & ruled-out
- What goes here
- Your best guess at root cause, plus what you eliminated. This is what makes it senior.
- Section
- Frequency & reach
- What goes here
- How many users, which versions, how often. Ties the bug to prioritization.
| Section | What goes here |
|---|---|
| Impact & severity | One line: what breaks, how bad (data loss / security / outage > broken feature > annoyance) and the reach. |
| Repro steps | Numbered, exact, copy-pasteable. Include the minimal repo or fixture if you built one. |
| Expected vs actual | Two short lines. The gap is the bug. |
| Environment | OS, Cursor version, model, plan, relevant extensions, network shape. |
| Evidence | Logs, HAR, screen recording, stack trace - attached, not described. |
| Hypothesis & ruled-out | Your best guess at root cause, plus what you eliminated. This is what makes it senior. |
| Frequency & reach | How many users, which versions, how often. Ties the bug to prioritization. |
Top to bottom is roughly the order an engineer reads in.
Severity times reach drives the queuehow the report feeds prioritization
A bug report isn't just a fix request - it's an input to the backlog. Quantify both axes so triage doesn't have to guess.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
Quantify both axes in the report so triage can place the bug without re-deriving it.
Data loss, security or outage sits at the top - these jump the queue.
Broken core workflow (Tab, Agent, chat) next.
Cosmetic or edge-case annoyance last.
How many users, weighted by ARR where it's known.
Which versions and platforms.
Frequency: every session or once a month?
An engineer can grasp the issue and the next step in under a minute.
Repro steps are exact enough to follow without you in the room.
Every claim is backed by an attachment, not a memory.
Your hypothesis names a cause and you've said what you ruled out.
“High severity, ~40 users on 0.42 across macOS: Agent overwrites unrelated functions on multi-file edits, 6 of 20 runs on this 3-file fixture (attached). HAR shows the edit payload returning a diff that spans files the prompt never named. I ruled out extensions (clean profile) and model (repros on two). My hypothesis: the diff-apply step isn't scoping edits to the targeted ranges.”
Takeaway. Lead with impact and severity, give exact repro steps with attached evidence and state a hypothesis plus what you ruled out - the report should hand an engineer the issue and the next step in under a minute.