Skip to content
PrepMint

How it works

What Cursor actually looks at when you ask it something

Indexing says synced, the file is open, and Cursor still answers as if your code does not exist. Follow one request end to end: what gets attached, what gets injected, what the agent decides to search for, what three ignore files remove, and what gets compressed away.

Rohan Surve10 min read

Last reviewed

You asked Cursor why the checkout flow throws on retry, and it answered confidently about a PaymentService that does not exist in your repository. Or it said it could not find the file you have had open all afternoon. Indexing says synced. The file is right there in the tab bar.

The reflex is to go looking for a setting, and that is the wrong layer. Cursor documents indexing as something you switch on and @-mentions as something you type; neither answers the narrower question you actually have: is this file in front of the model on this request? Two questions that look identical can get different answers ten seconds apart, same repo, nothing reconfigured.

So follow one request instead. Between pressing enter and a model having an input, seven things add text, exclude text or throw text away, and a different party decides each one.

The model never sees your repository, only a request

A language model has no filesystem handle. Every turn, Cursor assembles one payload of text and sends it, and that payload is the model's entire world for the turn.

Cursor will show you the payload. The context ring beside the chat input opens a breakdown tray splitting the request by category: system prompt, tool definitions, rules, skills, MCP instructions, subagent documentation, summarised earlier turns, and the conversation itself. "Every chat shares a fixed context window with the model. As you add files, run tools, and exchange messages, those tokens fill up."

Notice what is missing: there is no "your codebase" row. Your source files arrive as tool results, attached to a message, or not at all.

What you attach before you press enter

Two things put file content into the request before the model acts: what you attach, and what Cursor picks up from your editor.

Attachment is @ — files, folders, terminal output, previous chats, git diffs, the built-in browser. It is the only mechanism on this page with a guaranteed outcome: you named it, so it is there.

Editor state is the soft half. Open tabs, the current selection and linter errors are signals Cursor can attach on your behalf: a heuristic, not a promise. Which matters, because a common wrong inference sounds like this: the file was open, so it saw it. Open is not attached.

Cursor's own guidance draws the line: "If you're not sure which files matter, skip it — Agent finds relevant files through its own search." That hands the decision to a model's judgement.

What Cursor adds whether you asked for it or not

Four of those tray rows — system prompt, tool definitions, MCP instructions, subagent documentation — are prepended whether or not you asked. The fifth is rules, and rules are the piece people forget lives in the same budget as everything else. Project, user and team rules are merged into the request as text, occupying the same window as the file you wanted read. That is the only claim this article makes about them — which rule types exist, why a plain .md in .cursor/rules is invisible, and why a team rule can quietly outrank yours belong to why Cursor is ignoring your rules file; where a given instruction should live at all is where an instruction belongs.

Cursor's agent does not read your repository the way a compiler does. It emits a tool call. "Tools are the building blocks of Agent. They are used to search your codebase and the web to find relevant information, make edits to your files, run terminal commands, and more." Searching for text, reading a file and listing a directory are separate tools, and each runs because the model decided, from your wording, that running it was worth a turn.

Two consequences. Your file's contents enter the request only after a round trip — the model asked, Cursor executed, the result came back as a new block of text — so on the first pass through your question, your code is not there yet. And a search that never happened looks, from where you sit, exactly like a search that found nothing. There is no limit on the agent's tool calls, so the constraint is never budget; it is judgement about whether searching is warranted, worked through in your MCP server connects and the tools still do not run.

The index makes a file findable, not present

Indexing and context get conflated constantly, and the general form of it — a search decided what reached the model, so being findable is not being read — is worked through in why ChatGPT and Claude didn't really read your file. Cursor's version of it is worth having on its own, because the product gives you a switch for each half.

What indexing produces is embeddings, not a copy of your code in a chat: "Cursor creates embeddings without storing filenames or source code. Filenames are obfuscated and code chunks are encrypted. When Agent searches, Cursor retrieves the embeddings and decrypts the chunks on the client side."

So the index is a lookup structure that makes searching by meaning possible. It is not the thing the model reads. An indexed file is one the agent could find if it searched with the right intent. "Indexing is at 100%" and "the model knows about my code" are unrelated statements.

The proof that Cursor treats these as two systems is that it gives you a switch for each: "Use .cursorindexingignore to exclude files from indexing only. These files remain accessible to AI features but won't appear in codebase searches." Out of retrieval, still readable when named. Findable and present are different properties.

Since retrieval decides what the agent finds, your phrasing decides which retrieval runs. Name a symbol — a function, an error class, a config key — and the agent reaches for grep. Cursor ships its own grep with full regex and word-boundary matching, so import.*PaymentService traces exact references across files: a match is a match. Describe a behaviour instead — "how do we handle a failed payment" — and there is no string to match, so it searches by meaning against the embedding index and takes what ranks highest.

When you know the string, give it the string. Recall on an exact search is a property of your repository; recall on a semantic search is a property of a ranking function you cannot inspect, and the file you meant may sit just below the cut.

What is excluded, and from which half

Exclusion is where "Cursor cannot see my code" is usually decided, and there are three mechanisms with three different reaches.

.gitignore comes first, and it is not opt-in: "Cursor automatically respects your .gitignore patterns. Files ignored by git are also ignored by Cursor's indexing." Build output, generated clients and vendored code are therefore missing from search, and Cursor's troubleshooting page goes further: .gitignore patterns can prevent the agent from discovering files at all.

.cursorignore is the hard block. Files listed there are cut off from the agent, from Tab and Inline Edit, and from @-mentions — the one documented exception to "an @-mention always works". The caveat matters too: terminal commands and MCP server tools run outside these controls, so an ignored file can still come back through cat.

.cursorindexingignore is the soft one above. Underneath all three sits a default list you did not write: lockfiles, .env*, images, archives and fonts are excluded from indexing automatically, and so — the ones that surprise people — are *.csv, *.pdf and *.svg. Ask about a data file, get a confident guess, and that is often why. The negation rule has a sharp edge too: excluded directories are not traversed, so !public/assets/style.css under a public/* rule does nothing until you un-exclude the directory.

Why a monorepo behaves differently from a small repo

Same product, materially different behaviour, and it is not a defect.

"For large repositories, initial indexing can take time." Until it finishes, search by meaning runs against a partial picture, which is not distinguishable from a wrong answer.

The subtler effect is competition. Semantic retrieval returns a bounded number of chunks, so in a repository with four UserService.ts files across four packages, the one you meant competes with three plausible neighbours for the same slots; in a fifty-file project it wins by default. Letting the agent search costs almost nothing in a small repo. In a monorepo you are asking a ranking function to guess between near-duplicates.

Everything in the request has to fit, and old turns get compressed

The window is fixed and every row in that tray shares it — system prompt, rules, tool definitions, attachments, history — which is the same accounting what actually uses up your Claude usage limits works through as a bill; here it is a capacity rather than a cost. When the request approaches full, Cursor compresses older parts of the conversation into a summary to make room. Hence a failure that feels like dishonesty: a file the agent read twenty turns ago may now exist in the request only as a sentence saying it read it. The model is not bluffing when it gets a detail wrong — it no longer has the text.

Note what survives and what does not. Rules, tool definitions and the system prompt are re-injected every turn by construction; file contents are conversation history, and history is what gets summarised. That asymmetry explains a specific symptom: the agent follows your conventions perfectly while forgetting the function you showed it. Answers drifting between runs for related reasons is why the same prompt gives you a different answer. So if a file matters to the turn you are on, @-mention it again rather than trusting it is still in there.

A new chat starts with none of it

The request is assembled from scratch each turn out of the current conversation, so a new chat starts with no tool results at all. Every file the agent read and every search it ran are gone; what comes back automatically is only what is injected by construction — system prompt, tool definitions, rules. That is why a fresh chat appears to have forgotten a file it read thirty seconds earlier. Nothing was forgotten; the text is not in the new payload. Switching mode does the same, because each mode carries its own context. Starting fresh is also the highest-leverage thing you can do to a thread that is costing too much — its own article — and the two readings are the same event priced differently: you buy the saving by throwing away every tool result.

Cursor has a partial escape hatch, and characteristically it is another tool call: "Conversation search is available as an agent tool. When Agent needs context from something you discussed before, it can query your past conversations on its own." Even remembering is a retrieval decision.

Predicting whether a given file will be in front of the model

Five questions, cheapest first.

  1. Is it excluded? Check .gitignore, .cursorignore and the default list. If it is, stop — nothing downstream matters. git check-ignore -v <file> answers half of this in a second.
  2. Did you `@`-mention it on this turn? If yes, it is there. Done.
  3. Does your wording give the agent a reason to search for it? "Fix the login bug" supplies neither a string to grep nor a concept specific enough to rank your file first.
  4. Was it read earlier in this conversation, and has the conversation stayed short enough that the read has not been summarised away?
  5. Is this the same chat and mode it was read in?

If Cursor says outright that it cannot find a file, its own troubleshooting order matches: "Check your .cursorignore file in the project root. Files listed there are blocked from Agent, codebase search, and @ mentions." Then check .gitignore, reindex from the command palette, and attach the file directly.

One habit follows from all of it. Retrieval is a guess made on your behalf, and a good one most of the time. When the answer matters, name the file.

Practise this topic

Reading about it is half of it. Answer some questions on the same material — scored instantly, explanation for every one, no login.

Sources

Rohan Surve

Rohan Surve is a Technical Product Manager in Bengaluru with 12 years in enterprise delivery and 0→1 product building. He works with Claude, Claude Code, Cursor and MCP daily, and built PrepMint after noticing how much of what he "knew" about these tools was half-remembered from documentation he'd skimmed once. Every question here is written from working with the tool and checked against the vendor's own docs.

More articles by Rohan Surve

Question banks on this subject