How to fix Cursor codebase indexing issues on large repositories
Reinstalling Cursor rarely fixes a broken index. On monorepos above roughly 50,000 files, an aggressive .cursorignore plus a manual reindex almost always does instead.

How to fix Cursor codebase indexing issues on large repositories
Cursor's usefulness depends on a local vector index of your codebase, and on monorepos above roughly 50,000 files that index frequently, stalls at a fixed percentage, silently skips whole packages, or goes stale after a big rebase. The fix is rarely "reinstall Cursor." It is almost always a combination of an aggressive.cursorignore, manual reindex and realistic expectations about what a vector index can hold. This guide walks through the diagnosis and the fix in the order I actually use them.
What Cursor's index actually does (and why big repos break it)
Cursor chunks your files, embeds those chunks, and stores the vectors locally so that chat and inline edits can pull relevant context without sending your entire repo to a model on every request. This is retrieval-augmented generation applied to your codebase: instead of "read everything," the model gets "read the twelve chunks that look most related to this query." According to Cursor's own documentation on codebase indexing, the indexer walks your file tree, respects ignore files, and computes embeddings in the background, showing progress in the settings panel.
The problem with large repos is volume plus noise. A monorepo with a dozen services, a node_modules tree per package, generated protobuf output, and a few gigabytes of fixture JSON gives the indexer millions of low-value tokens to chew through before it gets to your actual source. Two failure modes show up constantly:
- Indexing never finishes. It sits at some percentage for hours because it is still embedding vendored dependencies or build artifacts you never asked it to touch.
- Indexing "finishes", but the index is wrong. Cursor answers confidently using a stale snapshot from before your last major refactor, or it hallucinates a function signature because the actual file was too large to fully chunk and got truncated.
Both are usually fixable without filing a support ticket, and both come down to the same root cause: the index is trying to represent too much, or the wrong things.
How do I check Cursor's indexing status?
Open Cursor, go to Settings > Indexing & Docs (in current builds this lives under the gear icon, then the Features or Indexing tab depending on your version). You should see:
- The current indexing state (Indexing, Indexed, Paused, or Error)
- A rough count of indexed files versus total files discovered
- A button to pause, resume, or force a full reindex
If the percentage has not moved in ten or fifteen minutes on a repo with tens of thousands of files, that is not necessarily broken, embedding is genuinely slow on first run, but it is worth checking two things before you wait longer. First, look for an error state or a small warning icon next to the indexing row: Cursor will sometimes flag files it could not read (permission issues, binary files it tried to parse as text, or files exceeding its size ceiling). Second, check whether your machine's CPU or network is pegged. Local embedding calls for the initial index go out over the network to Cursor's embedding endpoint in batches, so a flaky VPN or corporate proxy can make indexing crawl without ever throwing a visible error.
If chat answers reference code that no longer exists, that is a different symptom than a stuck progress bar. That is a stale index, and the fix is a manual refresh, covered below.
Writing a .cursorignore that actually helps
.cursorignore uses the same pattern syntax as .gitignore and lives at your repo root (you can also nest one per package in a monorepo). Cursor reads it when building the index, so anything matched here is never embedded, which means it never shows up in the chat context and never slows down indexing.
Here is a starting template that covers the three biggest offenders on most JavaScript, Python, and mixed monorepos: build output, dependency trees, and oversized data files.
1# Dependencies2node_modules/3vendor/4.venv/5venv/6__pycache__/7*.egg-info/89# Build output and generated code10dist/11build/12out/13.next/14.turbo/15coverage/16*.min.js17*.map18target/1920# Large or generated data files21*.lock22package-lock.json23yarn.lock24pnpm-lock.yaml25*.log26*.sqlite27*.db28data/fixtures/**/*.json29**/*.snap3031# Media and binaries32*.png33*.jpg34*.gif35*.pdf36*.zip37*.tar.gz3839# IDE and OS noise40.DS_Store41.idea/42.vscode/A few notes on why each block earns its place. Lockfiles like package-lock.json or pnpm-lock.yaml can run tens of thousands of lines and contain zero information useful to an LLM answering questions about your application logic; they are pure indexing weight. Test snapshot files (*.snap) are similarly voluminous and rarely what you want surfaced in chat. Large fixture JSON is the one that catches people off guard: a single data/fixtures/large-dataset.json at 40MB can dominate the embedding queue and starve the rest of the repo of indexing time.
Restart indexing after editing .cursorignore, either by toggling it off and on in settings or via the manual reindex trigger described next. Cursor does not always pick up ignore file changes instantly on an already-running index.
Forcing a manual reindex
Stale context is the more insidious problem because nothing looks broken. Cursor shows "Indexed," chat responds fluently, and it is simply wrong: describing a function that was deleted three sprints ago, or missing a new module entirely.
To force a fresh index in the current Cursor builds:
- Open Settings > Indexing & Docs.
- Find your workspace in the indexed codebases list.
- Click the resync or "Reindex" action (the exact label varies slightly between Cursor versions, but it is next to the codebase name).
This deletes the local vector store for that workspace and rebuilds it from scratch. On a large monorepo, expect this to take real time (minutes, not seconds), so do not panic if chat context feels thin for a few minutes immediately after triggering it.
A cheaper alternative for day-to-day staleness: after a large merge or rebase, just close and reopen the workspace. Cursor does incremental reindexing on file change in most workflows, but incremental updates can lag behind bulk changes like a rebase that touches hundreds of files at once. A full resync is the reliable fix when you suspect drift, not a five-minute wait.
The trade-off nobody mentions: excluding too much
It is tempting to be maximalist with .cursorignore, exclude every directory you do not personally write code in, and call it done. This backfires in a specific way: Cursor's chat and inline edit features can only ground answers in what is indexed. If you exclude your types/ directory because it is auto-generated from an OpenAPI spec, you have also removed the one source of truth for your API's request and response shapes. The model will then guess at field names based on usage patterns elsewhere in the code, which is exactly the kind of confident-sounding hallucination this guide is trying to eliminate.
The same applies to configuration. Excluding *.yaml broadly because your Kubernetes manifests are noisy also hides docker-compose.yaml or CI config that a developer might reasonably ask Cursor about. A good rule of thumb: exclude by directory and by clear generated-artifact patterns (dist/, *.min.js, lockfiles), but be cautious about excluding entire file types wholesale unless you have checked that nothing load-bearing lives in that extension.
If you are unsure whether a directory matters, a middle path works well: keep it out of the default ignore list, and instead rely on Cursor's per-query context controls (referencing specific files or folders with @ in chat) when you need something narrow. That way, the broad index stays available as a fallback, and you are not permanently blinding the model to a directory you might need context from next week.
When large-repo indexing is the wrong problem to solve
Sometimes the real issue is not indexing hygiene, it is repo shape. If your monorepo is genuinely enormous (multiple unrelated products in one git tree, each with its own dependency graph), pushing Cursor to index the whole thing well is fighting the tool. In that situation, two options work better than an ever-growing .cursorignore:
- Open Cursor scoped to a subdirectory (the specific service or package you are working in) rather than the monorepo root. This gives you a smaller, denser index that is faster to build and more relevant per query.
- Use workspace-level settings if your team maintains multiple Cursor workspace configs, one per major service, so indexing scope matches how people actually work day to day, rather than matching the git repo boundary.
This is a real limitation worth naming plainly: vector search over code degrades as the corpus grows more heterogeneous, because "most similar chunks" start pulling in irrelevant matches from unrelated parts of the tree. A tightly scoped 5,000-file service index will usually outperform a loosely filtered 200,000-file monorepo index for accuracy, even though the second one technically "sees more."
Practical workflow for a monorepo team
Here is the sequence I run when a teammate reports "Cursor doesn't know about my code" on a large repo:
- Check indexing status in Settings > Indexing & Docs, confirm it says Indexed and not stuck or errored.
- Look at the file count versus what git ls-files | wc -l reports, minus the ignore file, to sanity-check the indexer actually covered what you expect.
- Add or tighten .cursorignore using the template above, adjusted for the stack (Go monorepos need vendor/ and *.pb.go, Python needs .venv/ and __pycache__/, Rust needs target/).
- Trigger a manual reindex, then wait for it to complete before judging the result.
- If answers are still wrong for a specific area, check whether that area is oversized (a single file over a few thousand lines splits poorly into chunks) and consider splitting it, or scope a second Cursor workspace to just that package.
For teams standardising this, commit the .cursorignore to the repo so every developer gets the same indexing behaviour rather than each person hand-tuning their own, which is also where a lot of "it works on my machine" confusion about Cursor's accuracy actually comes from. If you want the canonical, up-to-date list of what Cursor's indexer respects and how embeddings are computed, the codebase indexing docs are the source to check before assuming a bug, and the general Cursor documentation covers related settings like per-workspace configuration that affect indexing scope.
Related Articles

How to configure Model Context Protocol servers in Cursor
An agent that can run SELECT * FROM orders can usually also run DELETE FROM orders unless you stop it. Here's the exact MCP config, and where to draw the read-only line.

How to configure .cursorignore to prevent context pollution and protect secrets
Cursor indexes your working directory, not just what Git tracks, so an untracked .env file can still leak straight into a prompt. Here's how to lock that down properly.

Mastering Cursor Rules: A Practical Guide for AI-Native Development
Most teams set up Cursor Rules once and never touch them again, then wonder why the AI keeps repeating a mistake. Here are the four rule modes and how to keep a directory from going stale.