Cursor in a bun or pnpm monorepo: making the indexer not lose its mind
Published 2026-03-29 by Owner
Cursor’s codebase indexer assumes npm-style monorepos. In bun or pnpm workspaces, the structure is similar but the symlinks behave differently. Without configuration, Cursor’s indexer either:
- Re-indexes every package from each workspace (duplicates)
- Misses some files because of how it traverses symlinks
- Indexes vendored dependencies as part of your codebase
- Times out on first index because of the duplicate work
I’ve worked in both bun and pnpm monorepos with Cursor. Here’s the configuration that resolves the issues.
The .cursorignore for bun monorepos
# Bun symlinks
**/node_modules/
.bun-cache/
# Bun-specific generated artifacts
**/.bun/
bun.lockb
# Standard build outputs
**/dist/
**/build/
**/.next/
**/.turbo/
# Don't index workspace package symlinks; index the source
# (assuming workspace packages live in packages/)
node_modules/@your-org/
# Generated code
**/*.generated.ts
**/generated/
The key line is the workspace symlink exclusion. Bun creates symlinks under node_modules/@your-org/ that point to your workspace packages. Without excluding these, Cursor indexes the same code twice — once via the source path, once via the symlink. Duplicates confuse the search ranking.
The .cursorignore for pnpm monorepos
# pnpm symlinks
**/node_modules/
# pnpm-specific
.pnpm-store/
**/node_modules/.pnpm/
# Standard build outputs
**/dist/
**/build/
**/.next/
**/.turbo/
# Generated code
**/*.generated.ts
**/generated/
pnpm uses a content-addressable store under node_modules/.pnpm/. Excluding node_modules/ and .pnpm covers most of it, but .pnpm-store (the global store, sometimes within the project) needs to be excluded explicitly if it’s there.
What you should NOT exclude
A common mistake: excluding node_modules/ entirely and assuming Cursor will figure out workspace dependencies. It won’t. Cursor doesn’t read package.json to understand workspace topology.
You want Cursor to see the source of your workspace packages. That means the source should be in your indexed area. The exclusion is for the symlinks in node_modules/@your-org/, not the source in packages/your-org/.
Verifying the index
After updating .cursorignore, force a re-index (Settings → Codebase → Re-index). Watch the indexing progress.
The expected timing for a 200k-line monorepo:
- Initial scan: 30-60 seconds
- Indexing: 5-10 minutes for first time
- Subsequent re-indexes: 1-2 minutes
If indexing takes >20 minutes, check the resource usage. If Cursor is using 8GB+ of memory, it’s probably indexing duplicates. Re-check your .cursorignore.
Workspace-aware searches
After the index is right, Cursor’s chat searches understand the workspace. Asking “where is X used?” returns results across the workspace, not just within the active package.
A useful trick: when working in a single package and you don’t want cross-workspace results, scope your @-mention to the active package’s directory:
@apps/web/src/...
This narrows the model’s search to the current package. For tasks that don’t need cross-package context, this is faster and produces better results.
A specific failure I hit
In a pnpm monorepo, I had a package called @org/utils that lived at packages/utils/. Cursor’s search for someUtilFunction was returning:
- The function definition in
packages/utils/src/someUtilFunction.ts - The function definition in
node_modules/.pnpm/@org+utils@0.0.0/node_modules/@org/utils/dist/someUtilFunction.js - The function definition in three different
apps/*/node_modules/@org/utils/...paths
Five copies of the same thing, all in search results. The model would pick the wrong one (often the dist version) and start writing code based on the compiled output rather than the source.
The fix: aggressively exclude node_modules/ everywhere, including the nested ones. After the fix, the search returned the source path only. The model wrote correct code based on the actual implementation.
Multi-root workspaces (don’t)
VS Code (and Cursor) supports “multi-root workspaces” where you open multiple folders side-by-side. For monorepos, this is tempting — open just apps/web and packages/utils instead of the whole repo.
Don’t. Cursor’s indexer treats multi-root as separate projects. Cross-package searches don’t work. Refactor-across-packages doesn’t work.
Open the monorepo from the root. Use .cursorignore to control what gets indexed. Use per-directory .cursor/rules/ files (described in another guide) to scope behavior per package.
Performance after configuration
Before the configuration above, on a real bun monorepo (~180k lines across 14 packages):
- First index: 32 minutes
- Memory: 9GB peak
- Search latency: 3-5 seconds for cross-workspace queries
After:
- First index: 6 minutes
- Memory: 2.4GB peak
- Search latency: under 1 second for cross-workspace queries
The configuration is worth the time it takes (about 15 minutes once). For monorepos that grow over time, the indexing performance compounds — what’s a 6-minute first index now is a 40-minute first index in a year if you don’t manage what gets indexed.
What I’d ask Cursor to add
A cursor-workspace.json schema that explicitly declares “this is a monorepo, here’s where the source packages live, here’s where the build outputs are” would resolve most of these issues without users having to write .cursorignore by hand. The information is in the workspace’s package.json already (the workspaces field); Cursor could read it.
Until that ships, the manual configuration is the way to keep Cursor sane in a non-npm monorepo.