Cursor keeps breaking my code: how to escape the AI fix loop
You know the pattern. You ask Cursor to fix one error. It fixes it and breaks something else. You paste the new error, and it reverts its first fix. Twenty minutes later you're back at the original error, the file has three copies of the same function, and a feature that worked this morning is gone.
This is the AI fix loop, and it isn't unique to Cursor. Windsurf, Bolt, Lovable and Replit Agent all do it. The good news is that it's mostly predictable, and you can get out with a fixed routine. This guide explains why it happens and gives you a step-by-step way to break it.
Why the loop happens
Three mechanics work together:
- The model only sees part of your project. It edits
checkout.tswithout seeing thatcart.tsdepends on the function signature it just changed. The new error is a direct result of the fix. - The chat history becomes poison. After several failed attempts, the conversation is full of wrong hypotheses and half-applied diffs. The model keeps building on them. Cursor's own forum has threads about agents that don't realise an edit was already applied and keep applying it again, producing duplicate code. Cursor staff's suggested workaround is to stop the agent and start a new chat.
- It treats symptoms, not causes. Asked to "fix the error", a model will happily add
as any, wrap the code intry/catch, or delete the failing check. The error goes away, the bug stays, and it comes back somewhere else.
Most loops also share one root cause the model can't fix by editing code: a missing env var, an unapproved redirect URL, a database policy, or a package version conflict. If the real fix is in a dashboard, every code edit is a wasted move.
Step 1: stop, and save what works
The first move is to stop the agent. Don't send "try again".
git status # what has the agent touched? git diff --stat # how much? git stash # park the mess (or commit it to a branch) git log --oneline -15 # find the last commit where things worked
If you never committed, start now. One commit per working step is the best protection against AI regressions. In Cursor, checkpoints in the chat can restore earlier states, but git is more reliable and works across chats. If you can name a commit where the feature worked, you can use git bisect or a plain git diff <good> HEAD to see exactly what changed.
git switch -c rescue <last-good-commit> # clean branch from a working state npm ci && npm run build # confirm it really works
Step 2: get the real error, once
Run the thing that fails and copy the first error, in full, with the file and line:
- Build errors:
npm run build(notdev, which hides type errors). - Type errors:
npx tsc --noEmit. - Runtime errors: the browser console and the Network tab on the failing page.
- Server errors: the host's function logs (Vercel → Deployment → Logs; Supabase → Edge Functions → Logs).
Later errors are often consequences of the first one. Fixing error #7 first is a common way loops start.
Step 3: new chat, narrow context, diagnosis before edits
Open a new chat. Attach only the files that matter, using @ in Cursor: the file named in the error, its direct imports, and config files if relevant (package.json, tsconfig.json, vite.config.ts). Then ask for a diagnosis, not a fix:
Error (from `npm run build`, full output): src/pages/Checkout.tsx:42:18 - error TS2339: Property 'total' does not exist on type 'Cart | undefined'. Context: this worked at commit a1b2c3d. Since then `useCart` was changed. Task: explain the root cause in 3 sentences. List the files that would need to change. Do NOT edit anything yet.
Read the explanation. If it doesn't make sense, or it blames something unrelated, the model is guessing. Give it more evidence, not more chances.
Step 4: one small change, then verify
When the diagnosis looks right, ask for the smallest fix and set limits:
Apply the minimal fix in src/hooks/useCart.ts only. Rules: no refactoring, no renaming, don't touch other files, no `any`, no `@ts-ignore`, don't delete existing checks.
Then verify it yourself, outside the chat: npm run build, click through the feature, and commit if it's green. One fix, one check, one commit. If the fix needs changes in four files you didn't expect, stop and rethink. That's often a sign the "fix" is really a rewrite.
Step 5: add rules so it doesn't happen again
Cursor reads project rules from .cursor/rules/ (the older .cursorrules file still works in many setups). Other tools have equivalents, like Windsurf rules, Lovable's knowledge file and Bolt's project prompt. Here's a short set that blocks most loop behaviour:
# .cursor/rules/guardrails.mdc --- description: Editing guardrails alwaysApply: true --- - Change only the files needed for the current task. Never refactor unasked. - Never delete working code, tests, or validation to make an error disappear. - No `any`, `@ts-ignore`, or empty catch blocks as a fix. - Never create a duplicate component/file to satisfy an import; fix the import. - Do not change package.json or lockfiles without asking first. - If the same error survives two attempts, stop and explain instead of editing. - After each change, tell me the command to verify it.
The last two rules matter most. Tools like Aider cap automatic retries and hand control back to the user for the same reason: past a certain point, retrying only digs the hole deeper.
Signs you're fixing the wrong layer
If any of these apply, more code edits won't help:
- It works locally but not deployed. Check env vars, redirect URLs and rewrites. See works in preview, breaks after deploy.
- Empty data with no error. That's a database policy, not React. See Supabase redirect and RLS fixes.
- The error names a package or
node_modules. Check versions and lockfile state (npm ls <pkg>), not your components. - The error message changes every run. You may have two copies of a file, or a stale build cache. Delete
.next,distandnode_modules/.vite, then reinstall.
When the codebase is too tangled
Sometimes several sessions of loops have left the project in a state that's hard to reason about: duplicate hooks, dead files, half-migrated libraries. Be honest about what's cheaper:
- Roll back to the last good commit and re-apply only the changes you actually wanted, one at a time. This is usually faster than untangling.
- Freeze and fix types first. Running
tsc --noEmitto zero errors gives the AI (and you) a reliable map of what connects to what, and many loops stop on their own. - Get a second pair of eyes. A different model, a colleague, or a tool that reproduces the build in a clean sandbox will often spot the one config value that a long chat has been stepping around.
Cheat sheet
- Stop the agent.
git stashor branch. - Find the last good commit.
- Get the first real error from a production build.
- New chat, minimal files, diagnosis only.
- Smallest fix, with explicit limits.
- Verify yourself and commit.
- Add guardrail rules, and stop after two failed attempts.
FAQ
Why does Cursor keep breaking working code?
The model only sees part of your project, builds on failed attempts left in a long chat history, and tends to fix symptoms (any, try/catch, deleted checks) rather than causes. Many loops are actually configuration problems that no code edit can fix.
How do I stop a Cursor agent stuck in an editing loop?
Stop the agent, save or stash the changes with git, find the last working commit, and start a new chat with only the relevant files attached. Ask for a diagnosis before any edits, then apply one minimal change and verify it yourself.
What should I put in Cursor rules to prevent regressions?
Limit edits to the files needed, forbid refactoring, deleting checks, any and ts-ignore, forbid duplicate files and unapproved package.json changes, and tell the agent to stop and explain after two failed attempts.
When should I roll back instead of fixing forward?
When several loop sessions have left duplicate files, dead code or half-migrated libraries. Rolling back to the last good commit and re-applying wanted changes one at a time is usually faster.