Open a moderately large project and Cursor’s status bar sits on Indexing 5% for half an hour, or it finishes and immediately re-triggers with your fans at max. Cursor isn’t crashed. As of June 2026 it already respects your .gitignore plus a built-in default ignore list (node_modules/, lockfiles, .env*, .git/, binaries), so the slow case is almost always a directory those defaults don’t cover or that something overrode: a build-output folder your dev server rewrites every save, a cache directory, or a monorepo subpackage’s artifacts. The file watcher sees constant changes and never settles.
Fastest fix: add the noisy build/cache directory to .cursorignore, add the same path to VS Code’s files.watcherExclude, then open Cursor Settings > Indexing and click Resync Index. Details and the full template below.
Which bucket are you in
Match your symptom to the cause before editing anything.
| Symptom | Most likely cause | Jump to |
|---|---|---|
Indexing stuck at a low % and never moves | A huge directory is being scanned (vendored deps, generated code, a leaked node_modules) | Cause 1 |
Index finishes, then re-runs every few seconds while npm run dev is up | Build output / hot-reload writes retrigger the watcher | Cause 2 |
| Loop stops the instant you kill the dev server | Same as above | Cause 2 |
| Fine in one repo, broken only in a monorepo | Subpackage artifacts (apps/web/.next, packages/ui/dist) stack up | Cause 3 |
Setting up indexing... forever, even on a tiny repo | Server-side or account issue, not your files | Cause 4 |
| Spinner forever with Privacy Mode on | Privacy/legacy setting blocking the embeddings upload | Cause 4 |
Common causes
1. A large directory the defaults don’t cover
Cursor’s built-in list already skips node_modules/, but vendored dependencies (vendor/, third_party/), generated SDKs/protobufs, ML checkpoints (.pt, .safetensors), or a node_modules pulled back in by a symlink or an odd workspace root will sit outside it. Each is two orders of magnitude bigger than your source.
How to spot it: count files in suspects.
# total files Cursor might be walking
find . -type f -not -path './.git/*' | wc -l
# the usual offender
find node_modules -type f 2>/dev/null | wc -l
If the project total is in the hundreds of thousands and Cursor stays on Indexing, a directory needs ignoring. Healthy source trees are typically <= 30000 files.
2. Build output retriggers the watcher (the loop)
This is the most common modern case. With hot reload, dist/, .next/, .svelte-kit/, and .turbo/ are rewritten on every save. Cursor sees the change events and re-indexes, producing an “indexed -> changed -> re-index” loop that pins a CPU core. Two things have to be ignored here: the index (so the files aren’t re-embedded) and the file watcher (so Cursor stops getting woken up at all).
How to spot it: Indexing reappears every few seconds while npm run dev runs and goes quiet the moment you stop it.
3. Monorepo subpackage artifacts
apps/web/.next, apps/api/dist, and packages/ui/storybook-static each look small, but together they add up to millions of files. A single root .cursorignore with bare folder names (dist/) only matches the root; nested copies need globs.
How to spot it:
find . -type d \( -name dist -o -name .next -o -name build -o -name .turbo \) -not -path '*/node_modules/*'
More than two or three hits with no ignore coverage is your problem.
4. Server-side or Privacy Mode (not your files)
If it reads Setting up indexing... forever on even a tiny repo, the files aren’t the issue. Codebase indexing uploads embeddings to Cursor’s servers; older Privacy Mode settings and the occasional backend incident block that. Check the Cursor status page first, then Cursor Settings > General > Privacy. Semantic search only becomes available at roughly 80% completion, so a stall near the end can also be a server-side embedding queue rather than a local scan.
Shortest path to fix
Step 1: Write a thorough .cursorignore
At the project root, create or edit .cursorignore. It uses gitignore syntax, so ** matches across directories. The entries below go beyond Cursor’s built-in defaults (you don’t strictly need node_modules/ since it’s a default, but listing it makes intent explicit and also hard-blocks it from @-mentions):
# Dependencies (defaults cover node_modules, listed for intent)
node_modules/
vendor/
third_party/
# Build output (match at any depth for monorepos)
**/dist/
**/build/
**/out/
**/.next/
**/.astro/
**/.svelte-kit/
**/.nuxt/
**/.output/
**/storybook-static/
# Caches
**/.cache/
**/.turbo/
**/.vercel/
**/.parcel-cache/
**/.pytest_cache/
**/__pycache__/
# Test coverage and large binaries
**/coverage/
**/.nyc_output/
*.safetensors
*.pt
# Logs
*.log
.cursorignore blocks files from indexing and from Agent, Tab, and @-mentions. If you want a directory excluded from the index but still readable when you @-mention or drag it into chat (legacy code, large fixtures), put it in .cursorindexingignore instead, same syntax. Note Cursor calls this best-effort: it does not absolutely guarantee an ignored file is never sent to the model.
Step 2: Stop the file watcher from waking Cursor
.cursorignore keeps files out of the index, but the VS Code file watcher underneath Cursor still fires on every build write, which is what keeps the loop alive. Add the same noisy paths to files.watcherExclude in Cursor Settings > settings.json (Cmd/Ctrl+Shift+P -> Preferences: Open User Settings (JSON)):
{
"files.watcherExclude": {
"**/node_modules/**": true,
"**/dist/**": true,
"**/.next/**": true,
"**/.turbo/**": true,
"**/.cache/**": true,
"**/coverage/**": true
}
}
This single setting is what actually ends the re-index loop on hot-reload projects.
Step 3: Force a full index rebuild
Editing .cursorignore does not auto-rebuild. The fastest trigger is the command palette (Cmd/Ctrl+Shift+P -> Reindex Codebase), or do it from the UI (the menu was renamed from the old “Features” tab):
Cursor Settings > Indexing -> Resync Index
To clear server-side state too, use Delete Index on that same panel, then Resync Index. For a stubborn loop, the heaviest reset is to quit Cursor and wipe the local workspace index cache:
# Quit Cursor first, then:
# macOS
rm -rf ~/Library/Application\ Support/Cursor/User/workspaceStorage/*/cursorIndex
# Linux
rm -rf ~/.config/Cursor/User/workspaceStorage/*/cursorIndex
# Windows (PowerShell)
Remove-Item -Recurse -Force "$env:APPDATA\Cursor\User\workspaceStorage\*\cursorIndex"
# Relaunch Cursor
Step 4: Keep build output in one ignored place
If indexing finishes but the dev server still triggers re-runs, make sure your tooling writes everything into already-ignored directories rather than scattering output beside your source:
// vite.config.ts
export default {
build: {
outDir: 'dist', // matched by **/dist/
},
cacheDir: '.cache/vite', // matched by **/.cache/
}
How to confirm it’s fixed
- Restart Cursor and reopen the project. The status bar shows
Indexing 0%briefly. - A mid-size project reaches
Indexedwithin 1-3 minutes; a large monorepo takes 5-10 minutes. Semantic search lights up at ~80%, so don’t panic if the AI works before it says 100%. - Open
Cursor Settings > Indexing & Docs > View included files(or check the “Files Indexed” count on the Indexing panel). A healthy mid-size project is roughly5000to30000files. Still at100000+ means an ignore rule isn’t matching, recheck your globs. - Start
npm run devand watch the status bar for a minute. IfIndexingno longer reappears, the watcher exclude worked.
Prevention
- Commit
.cursorignorewhen you create the repo and keep it in sync with.gitignore. - Use
**/globs for build/cache dirs so nested monorepo copies are caught, not just the root. - Mirror the noisy paths into
files.watcherExcludeonce, then forget about it. - After installing a dependency, switching build tools, or adding a code generator, run Resync Index manually instead of waiting for a loop.
- Reach for
.cursorindexingignore(not.cursorignore) when you want a folder out of search but still readable on demand.
FAQ
Do I even need .cursorignore if Cursor reads .gitignore?
Often no for node_modules and lockfiles, since those are in the built-in default list. You need it for anything not gitignored that’s still huge or noisy (vendored code, generated SDKs, build dirs you commit), and to hard-block sensitive files from the Agent.
What’s the difference between .cursorignore and .cursorindexingignore?
.cursorignore blocks a file from indexing and from all AI access (Tab, Agent, @-mentions). .cursorindexingignore only removes it from the index; you can still @-mention or drag it into chat. Use the second for legacy code and fixtures you occasionally reference.
Why does indexing restart every time I save a file?
Your dev server is rewriting a build/cache directory that’s still being watched. Add that path to both .cursorignore and files.watcherExclude (Step 2). The watcherExclude entry is the one that stops the wakeups.
It’s stuck on Setting up indexing... even on a tiny repo. Now what?
That’s almost always server-side, not your files. Check status.cursor.com, confirm you’re signed in, and look at Cursor Settings > General > Privacy since older Privacy Mode settings can block the embeddings upload. Then Delete Index and Resync Index.
How many files should “Files Indexed” show?
For a typical app, roughly 5000 to 30000. If you see 100000+, an ignore pattern isn’t matching, usually a bare dist/ that doesn’t catch nested monorepo copies. Switch to **/dist/.
Related
- Cursor missed the full project context
- Cursor keeps reading the wrong files
- Cursor cannot apply edits
- Multiple AI agents created conflicts
- AI pre-commit review workflow
Tags: #Cursor #AI coding #Debug