I've been running experiments on Cursor's rule system for a while now. I started because my rules weren't working and I couldn't figure out why, and I ended up going way deeper than I planned.
So if you're still using a .cursorrules file in your project root, it works, but it's the old way. Cursor moved to .cursor/rules/*.mdc files a while back. The .mdc format lets you scope rules to specific file types, set metadata in frontmatter, and organize rules into separate files instead of one giant blob. I tested both. If you have a .cursorrules AND .mdc files, the .mdc files win. The old file still loads in a clean directory with nothing else, but once you have the new format, that's what Cursor uses.
Migrating takes maybe 10 minutes. I split my monolithic .cursorrules into separate .mdc files by concern. One for TypeScript conventions, one for testing patterns, whatever.
alwaysApply: true or nothing
This one cost me WAY too much time...
Every .mdc file has YAML frontmatter at the top. There's a field called alwaysApply and if you don't set it to true, your rule just doesn't fire. I tested this 13 times because I was convinced something else was wrong. Nope. Without the flag? It got ignored. Every time. With it? It worked every time.
Your frontmatter needs to look like this:
---
description: "TypeScript conventions for this project"
globs: "**/*.ts"
alwaysApply: true
---
Without that last line, Cursor acts like the rule doesn't exist.
Be specific or get ignored
I tested vague rules vs specific ones at different lengths. Length doesn't matter AT ALL.
"Write clean, maintainable code." Longer version? Nah. Shorter version? Nah. Doesn't matter how many paragraphs you write elaborating on it. Your 'clean and maintainable' means the most generic of the generic.
"Use early returns instead of nested if blocks." That one it followed consistently, even as a single line.
Cursor isn't reading between the lines. It's not going to interpret "clean code" (or anything else) the way you mean it. You have to spell out exactly what you want unless what you want is...a universal standard, I guess.
Conflicting rules will stall the agent
I had two rules in separate .mdc files. One said "always add explicit type annotations." The other said "infer types when obvious." I didn't think this would be a big deal, I thought it could pick out the difference in context and use the right one when appropriate.
Cursor couldn't resolve the contradiction. The agent just sat there. It didn't error, it didn't pick one, it didn't try to merge them. Just... nothing. I thought it was a performance issue before I even thought to check if my rules were fighting each other. We just expect this stuff to work and in reality its brain can break as easily as ours when we're frustrated.
"Clean up this code" will delete your comments
This one STILL bothers me. I ran "clean up this code" on 17 different files. Every single comment got stripped. It didn't matter if the comment explained a browser workaround, a compliance requirement, a deprecation timeline. Cursor treated them all as noise.
Zero files kept their comments.
I added a rule that says "preserve all existing comments unless they contain TODO, FIXME, or are clearly outdated" and ran it again. Even then it only kept maybe half of them. Cursor seems to have a strong bias toward "clean = fewer comments" and you have to actively fight it.
I wrote a whole post about this one because I can see a lot of people not catching it in diffs.
Your frontmatter is probably broken
This is what finally pushed me to build something. Malformed YAML in your .mdc frontmatter fails completely silently. Missing colon, bad indentation, unclosed quote. No error, no warning. The rule just doesn't exist as far as Cursor is concerned.
I had rules that looked fine but weren't doing anything, and the reason was a missing colon in the frontmatter. It wasn't a logic problem or a wording problem. It was a typo.
So I built cursor-lint to catch this stuff. I just run it as a pre-commit hook now so I don't waste another afternoon wondering why a rule isn't firing. It checks frontmatter, validates required fields, flags conflicts between rules.
Rules vs skills
Cursor added "agent skills" (.cursor/skills/*.md) recently. I tested both on the same tasks and they both work.
The difference: rules with alwaysApply: true load on EVERY task, even unrelated ones. You have a TypeScript formatting rule and you ask Cursor to write a Python script? That TS rule still loads and can confuse things. Skills only load when the task is relevant.
Oh and one weird thing. .claude/skills/ files from Claude Code don't get discovered by Cursor's agent at all. Skills have to be in .cursor/skills/.
Model choice doesn't matter for rule compliance
I expected at least one model to be worse at following instructions but nope. I tested the same rules across Sonnet 4.5, Gemini 3 Flash, and GPT-5.1 Codex Mini. All three followed the rules at the same rate. Compliance is about rule quality, not model selection.
Negative vs positive framing doesn't matter either
"Do NOT use nested ternaries" vs "Use if/else blocks instead of nested ternaries." I ran 30 tests on this. No difference. 30/30 compliance both ways.
Write rules however feels natural to you. The framing doesn't matter, that's something that mattered for AIs a few years ago but not now.
What I use now
I have about 8 .mdc files in most projects. TypeScript conventions, comment preservation, testing patterns, a few framework-specific ones. Each file is short and specific. I run cursor-lint as a pre-commit hook and in CI through the GitHub Action.
The whole setup takes maybe 15 minutes for a new project and it saves me from the stuff I spent weeks debugging when I didn't know any of this.
📋 I made a free Cursor Safety Checklist — a pre-flight checklist for AI-assisted coding sessions, based on actual experiments.
Top comments (4)
yeah the alwaysApply thing is brutal because nothing tells you it's not working. it just silently does nothing. the pattern you're describing with splitting configs is spot on, i went through the exact same thing.
The comment stripping thing is so real. I lost an entire block of comments explaining a workaround for a Safari flexbox bug that took me hours to figure out. Didn't catch it in the diff because who reviews every deleted line when the agent says "cleaned up your code"?
The conflicting rules issue is interesting though - I wonder if there's a way to add priority/weight to rules so the agent knows which one wins. Like "prefer explicit types UNLESS the type is obvious from a literal assignment." That kind of conditional logic would make the whole system way more practical.
Also really appreciate that you actually ran controlled tests instead of just vibes. The "negative vs positive framing doesn't matter" finding alone probably saves people hours of overthinking their rule phrasing. Good stuff.
the safari flexbox stuff is exactly the kind of comment that looks like noise to the model but is critical context. i ended up adding a preserve-comments rule after losing something similar. it works most of the time but you still have to spot-check.