Tinker AI
Read reviews
intermediate 5 min read

Cursor's codebase index: when to leave it alone and when to nuke and rebuild

Published 2026-05-11 by Owner

Cursor keeps an index of your codebase that most of its AI features draw on: symbol lookup, codebase-wide chat context, inline completions that reference code you haven’t opened. Most of the time this index stays current without any input from you. It updates as you edit, as files change on disk, and when you switch branches.

Most of the time — until it doesn’t.

Developers often assume Cursor reads files on demand, so a wrong answer must be a model problem. The reality is more layered: Cursor is often reading a cached representation, and that cache can fall behind. Knowing when and how to reset it is the difference between debugging a phantom bug for an hour and fixing it in thirty seconds.

What the index actually is

Cursor’s indexing pipeline reads your files, extracts structure (identifiers, types, call sites, imports), and stores a compressed representation it can query at inference time. It’s not a raw copy of your source; it’s closer to a search index with semantic enrichment. When you ask “where is handleWebhook called?” in chat, Cursor is querying that index, not scanning your files live.

This matters because the indexed representation is faster than scanning raw source. Cursor can look up UserRepository across a 300-file TypeScript project in milliseconds; it reads the index, not 300 files. The tradeoff is that the index can drift out of sync.

The index is scoped to your workspace folder. Files matching patterns in .cursorignore (or .gitignore if no .cursorignore exists) are skipped. node_modules, build artifacts, and binary assets fall out by default. For most projects that’s fine.

Two things the index doesn’t capture: file content streamed in from outside Cursor (a running process appending to a log, for instance), and code generated into ignored directories. If you build into dist/ and haven’t excluded it, those files are indexed too — often a source of confusing completions that autocomplete generated boilerplate instead of hand-authored functions.

The size of the index scales with file count more than line count. A repo with 10,000 small utility files will index more slowly than a repo with 50 large files — trimming file count via .cursorignore has a measurable effect on startup time.

Cursor uses the index for Tab completions and @codebase references, not just chat. Staleness affects all of these equally.

Symptoms of a stale index

Index staleness has a recognizable pattern. Cursor starts behaving as if it’s working from an older version of the codebase:

  • Chat says it can’t find a symbol you added two hours ago, or refers to a function you renamed last week by its old name
  • Completions suggest import paths that don’t exist, or suggest import { Foo } from './bar' when that module was moved to ./baz in a recent refactor
  • “Find usages” in Cursor’s chat returns an incomplete list — it finds 3 call sites but you know there are 7
  • After a large merge, Cursor’s answers feel like they’re describing a previous state of the codebase
  • A new file you created isn’t recognized at all, even after saving and reopening it
  • Cursor keeps suggesting a deleted helper function as a solution to a problem, because the deletion didn’t propagate to the index

What makes staleness frustrating is that it looks like a model error. Better prompts won’t help when the model is working from an outdated data source. Reset the index first, re-evaluate after.

Not all of these are index problems. Cursor’s context window can also be too small for a complex question, and the model can hallucinate regardless of index freshness. But if the same question returns an answer that’s specifically wrong about something that recently changed, the index is the first thing to suspect.

The clearest diagnostic is to ask Cursor something with a known, recent answer. If you renamed fetchOrders to fetchOrdersByUser yesterday, ask in chat: “where is fetchOrdersByUser defined?” If Cursor says it can’t find it, or finds fetchOrders instead, the index is stale. That’s a narrow enough question that context window limits don’t explain the miss.

What updates automatically

Cursor reindexes incrementally under several conditions:

  • File saves: when you save a file, Cursor queues it for reindexing within a few seconds
  • Background file changes: modifications from git operations, npm install, or external tools are picked up on a polling interval
  • Branch switches: Cursor detects the working tree change and reindexes the diff
  • Workspace open: on startup, Cursor computes a delta between the current tree and the last indexed state

The incremental path is fast — usually under a second for a changed file. Cursor does not reindex unmodified files on every save, which keeps it snappy on large repos.

For most day-to-day work — editing files, adding functions, renaming variables within a file — the incremental path handles everything and you never think about the index at all. It’s only at the edges, where the change is structural or the volume is high, that the incremental path shows its limits.

The polling interval for out-of-editor changes is longer, typically 10–30 seconds. If you ran a code generator, switched branches in a terminal outside Cursor, or pulled a large remote update, the index may lag by up to a minute before auto-updating catches up. Waiting usually fixes it; a manual reindex is faster.

Branch switching is worth calling out specifically. When Cursor detects a branch switch, it diffs the working tree against the previous state and queues changed files for reindexing. This works well for typical feature branches where a few dozen files diverge from main. It works less well for branches that diverged weeks ago with hundreds of changed files — Cursor may process the diff correctly but the volume of updates can mean the index is partially stale for the first minute or two after the switch.

If you routinely switch between significantly diverged branches, making the post-switch resync a habit costs almost nothing and avoids periodically debugging index lag that presents as a model error.

The reindex command

When incremental updates aren’t enough, Cursor supports a full index rebuild. The command is Cursor: Resync Index (Cursor versions 0.40+).

To run it: open the Command Palette (Cmd+Shift+P on macOS, Ctrl+Shift+P on Windows/Linux), type resync, and select Cursor: Resync Index. There’s no keyboard shortcut by default; you can assign one in Keyboard Shortcuts if you trigger this often.

What it does: discards the existing index for the workspace and rebuilds it from scratch by scanning every non-ignored file. On a codebase of 50k lines, this typically takes 15–45 seconds. On 500k lines, a few minutes. Cursor’s status bar shows progress during the rebuild; AI features degrade to file-only context until it completes.

There’s no confirmation dialog and no undo — but the old index was stale anyway, which is why you’re here.

Running resync speculatively is always safe. If something feels off after a complex change and you’re not sure whether it’s a model issue or an index issue, run the resync first. It eliminates one variable; if answers are still wrong afterward, the index isn’t the culprit.

While the resync runs, Cursor still works: completions pull from open files and chat can answer questions about files currently open in the editor. What’s unavailable is cross-file index queries. For a 30-second rebuild on a mid-size codebase this is barely noticeable; for a large monorepo, expect partial cross-file answers until the rebuild finishes.

Cursor shows a loading spinner in the bottom-right status bar when indexing work is queued. If the spinner is gone and answers are still wrong, the problem is elsewhere.

When a full rebuild is the right call

Incremental reindexing handles individual file changes well. It struggles with several scenarios that all share a common thread: the structural relationship between files changed in bulk.

After a large branch merge. When a merge touches 50+ files across module boundaries — renaming exports, moving types, restructuring directories — the incremental updater may not correctly propagate all cross-file relationships. The index ends up with stale edges: it knows PaymentService exists, but still thinks it lives in src/services/payment.ts when it moved to src/modules/billing/payment.ts. A full rebuild is the only way to clean this up reliably.

After a major refactor that renames or moves many symbols. Same mechanism: the incremental updater handles “this file changed” well, but struggles with “these 40 imports across 20 files now point somewhere different.”

After adding a new language or framework to the monorepo. Cursor’s parser configuration is set at workspace open time. Adding a new packages/mobile/ directory with Swift or Kotlin files may require a restart plus resync for the new file types to be indexed correctly.

When you’ve moved or renamed directories in the file system outside Cursor. Directory-level moves are harder for the incremental updater to track than file-level changes. A resync after a directory rename takes the uncertainty out of it.

The pattern: if what changed was structural rather than textual, resync. If what changed was the content of known files, wait a minute and let the incremental path catch up.

Concretely: after a git pull that updated 5 files, wait. After a git merge feature/new-auth that touched 80 files across three packages, resync before doing anything else.

A workflow that prevents most of it

A few habits reduce how often a manual resync is needed:

Do large structural refactors inside Cursor rather than in a separate terminal. Cursor detects its own file operations synchronously; it only polls for external ones. Moving a directory in Finder or via a terminal mv command means Cursor has to poll to detect the change; doing the same rename via Cursor’s own file tree or a Cursor chat prompt means the index update is immediate.

After pulling a large remote update or finishing a merge, run the resync immediately rather than waiting to see if something feels wrong. Thirty seconds now beats five minutes of confused debugging later.

If a project has generated files that shouldn’t be indexed, add them to .cursorignore. The index rebuild is faster, and completions don’t get confused by code that was generated rather than authored. A common offender is __generated__/ directories from GraphQL codegen — the volume of identifiers floods autocomplete results.

Keep .cursorignore tight. Excluding real source files for performance reasons defeats the index’s purpose. Exclude generated output and large data fixtures; keep all authored source indexed.

One thing that’s worth knowing: the index is local to your machine, not shared. If you’re on a team and a colleague says Cursor is handling the new module well, their index may be fresh for unrelated reasons. The index state is per-developer, not per-repo.

On a fresh clone or after wiping Cursor data, the index rebuilds from scratch on first open. This can take several minutes on a large repo; AI features fall back to file-only context until it completes. The spinning indicator in the bottom-right status bar shows when indexing is in progress.

What a resync doesn’t fix

A resync addresses index staleness. It doesn’t address:

  • Model knowledge cutoffs — Cursor can’t know about a library released after its training data was collected, regardless of index state
  • Files you’ve excluded from indexing — if something is in .cursorignore, resyncing won’t add it
  • Context window limits — large codebases are partially indexed per query anyway; resync improves recall but doesn’t expand the model’s context window

If Cursor’s answers about recently-added code are wrong after a fresh resync, the issue is elsewhere: the file may be in an ignored path, or the question requires more context than fits in a single query.

A quick way to verify that a file is actually being indexed: open Settings > Cursor Settings > Features > Codebase Indexing. The panel shows a count of indexed files and lets you check the indexing status. If a file you expect to be indexed isn’t there, the most likely cause is a .gitignore pattern that’s excluding it.

One scenario that surprises people: Cursor respects .gitignore files anywhere in the directory tree, not just at the repo root. A .gitignore inside packages/api/ that contains *.ts will exclude all TypeScript files from that subtree from both git and the Cursor index. If a subdirectory’s code is mysteriously absent from index queries, check for nested .gitignore entries before suspecting an indexing bug.

Most of the time the index does its job quietly. When it doesn’t, the resync command exists exactly for this, and it’s fast enough that running it speculatively costs almost nothing. The mental model is similar to clearing an application cache: it’s not a fix for a fundamental problem, but it reliably eliminates one class of problem so you can focus on diagnosing the others.