· cursor / rules / ai

Cursor rules: Four modes and the .cursorrules trap

.cursorrules silently fails in Agent mode — 0/9 compliance. The four rule modes that work, five silent anti-patterns, and six annotated real-world templates.

By

2,275 words · 12 min read

If you’re using .cursorrules, stop — it does nothing in Agent mode. Migrate to .cursor/rules/*.mdc files with proper YAML frontmatter, keep your always-on rules under 2,000 tokens, and scope everything else by glob or description. That’s the summary. The rest of this article explains why, shows you what the rule engine actually does, and gives you six ready-to-use templates.

Who this is for

Developers using Cursor who write rules by instinct and aren’t sure why they work sometimes and do nothing other times. If you’ve never opened .cursor/rules/, start here first. If you’re comparing Cursor to other AI coding tools, see Cursor vs GitHub Copilot or Cursor vs Claude Code.

How the engine evaluates rules

Cursor’s rule system is documented at cursor.com/docs/rules. Three frontmatter fields in each .mdc file drive every activation decision: alwaysApply, description, and globs.

The engine runs in this order every time Agent Chat opens a new prompt:

  1. Always Apply rules are concatenated unconditionally — they’re in every session, full stop.
  2. Auto Attached rules are added when a file matching their globs pattern enters the conversation context.
  3. Agent Requested rules: their description fields appear in an <available_instructions> block. The model calls an internal fetch_rules tool to load any rule it judges relevant to the current task.
  4. Manual rules also appear in <available_instructions> but are never fetched unless you type @rule-name in chat.
  5. All matched rules merge and inject at the start of model context in this order: Team Rules → Project Rules → User Settings Rules. Earlier-appearing rules win on conflict.

This mechanism was reverse-engineered and documented by roman.pt, who traced the actual prompt structure Cursor sends.

One thing that trips up almost everyone: rules apply to Agent Chat only. They do nothing for Tab autocomplete or inline Edit (Cmd+K). If you’re wondering why your rules aren’t affecting suggestions, that’s why.

The four modes

Each rule file’s activation mode is determined by which frontmatter fields you set.

Always Apply

---
alwaysApply: true
---

Loaded unconditionally. Use sparingly — see anti-pattern 1 below.

Auto Attached

---
globs: ["src/components/**/*.tsx"]
alwaysApply: false
---

Injected when a file matching the glob enters context. The most useful mode for scoping rules to specific parts of the codebase.

Agent Requested

---
description: "Use when writing git commit messages or summarizing changes for a PR"
alwaysApply: false
---

The agent reads the description and decides whether to fetch the full rule for the current task. A good description is the entire mechanism — write it as an instruction to the model, not a title.

Manual

---
alwaysApply: false
---

No description, no globs. Cursor lists it in <available_instructions> but never fetches it automatically. You invoke it with @rule-name in chat. Right for step-by-step scaffold templates you want on-demand.

The .cursorrules trap

If you have a .cursorrules file in your project root and you’re using Agent mode, your rules are doing nothing.

The Prompt Shelf documented this with compliance testing: the same rule content as a .cursorrules file achieved 0/9 rule compliance in Agent mode. Converted to a .mdc file with alwaysApply: true, the same content achieved 9/9.

Agent mode uses a different execution path that does not read .cursorrules. Cursor gives no error, no warning — the file is silently ignored. You can tune a careful rules file for weeks and have zero effect on Agent output.

The comparison:

.cursorrules.cursor/rules/*.mdc
Agent mode✗ silently ignored
Tab autocomplete
Per-concern scoping✗ (one file, no frontmatter)✓ (one file per concern)
Team Rules (Enterprise)✓ required
Version control

When to keep .cursorrules: solo project, never using Agent mode, or publishing rules for community discoverability (most repos at PatrickJS/awesome-cursorrules use it because it’s the known format). For any agentic workflow, migrate.

Migration: move the content of .cursorrules into .cursor/rules/base.mdc, add alwaysApply: true frontmatter, and delete the old file. One command if you have yq installed:

echo "---\nalwaysApply: true\n---\n" | cat - .cursorrules > .cursor/rules/base.mdc
rm .cursorrules

Only .mdc extension files are read — .md files in .cursor/rules/ are silently ignored. Extension matters.

Template library

These six files cover a typical full-stack TypeScript project. Copy them into .cursor/rules/, adjust to your stack.

1. Project-wide base rule — Always Apply

For a Next.js 15 / TypeScript / Drizzle project. Keep it short; always-apply tokens are expensive. Source: techsy.io.

---
alwaysApply: true
---

# Project: Acme Dashboard

## Tech Stack
- Next.js 15 (App Router only — no Pages Router)
- TypeScript strict mode
- Tailwind CSS v4
- Drizzle ORM with PostgreSQL
- pnpm for package management

## Critical Conventions
- All components are React Server Components by default
- Use "use client" only when the component needs interactivity
- Import paths use @/ alias mapped to src/
- Error handling: wrap async operations in try/catch, never use .catch()
- No default exports except for pages and layouts

This is ~120 tokens. Small enough to keep always-on without context bloat.

2. React component rule — Auto Attached by glob

Scoped to component files. Only consumes tokens when a component is in context. Sources: techsy.io, morphllm.com.

---
description: "React component authoring rules"
globs: ["src/components/**/*.tsx", "src/app/**/*.tsx"]
alwaysApply: false
---

# React Component Rules

## Structure (in order)
1. Imports
2. Props interface (above the function)
3. Component function (named export)
4. Sub-components if any

## Patterns
- YES: `export function Button({ label }: ButtonProps)`
- NO: `export default function Button()`
- No barrel exports (index.ts re-exports)
- No prop drilling beyond 2 levels
- Extract sub-components when component exceeds 150 lines
- forwardRef for any component wrapping a DOM element

## Anti-patterns
- No useEffect for data fetching — use Server Components
- No CSS modules — Tailwind only

3. Testing rule — Auto Attached

Activates on test files. Source: PatrickJS/awesome-cursorrules categories + community patterns.

---
description: "Unit and integration testing conventions"
globs: ["**/*.test.ts", "**/*.spec.ts", "**/__tests__/**/*.ts"]
alwaysApply: false
---

# Testing Standards

- Describe blocks group related tests, not implementation details
- One assertion per test where feasible; max three
- Test file mirrors module path: src/utils/format.ts → src/utils/format.test.ts
- Use `it('should ...')` phrasing for behavior
- Mock at the boundary (external HTTP, DB), never internal module functions
- Each test is independent — no shared mutable state
- Prefer `userEvent` over `fireEvent` in React Testing Library

## What to test
- Happy path + two most likely failure modes per function
- Edge: empty arrays, null/undefined, empty strings
- NOT implementation details (private methods, internal state)

4. Commit message rule — Agent Requested

The description is written as a condition, not a title — that’s what the agent reads to decide whether to fetch it. Inspired by the “Git Conventional Commit Messages” category on PatrickJS/awesome-cursorrules.

---
description: "Use when writing git commit messages or summarizing changes for a PR"
alwaysApply: false
---

# Commit Message: Conventional Commits

Format: `<type>(<scope>): <subject>`

Types: feat | fix | refactor | test | docs | chore

- Subject max 72 characters, imperative mood ("add" not "adds")
- No period at end of subject line
- Reference issue in footer: `Closes #123`

Examples:
feat(auth): add Google OAuth sign-in flow
fix(api): handle null response from payment provider

The agent fetches this when you ask it to write a commit message, without you having to mention it.

5. TypeScript strict-mode rule — Auto Attached

Excludes test files via the ! glob negation so strict-mode guidance doesn’t conflict with test-specific patterns. Sources: stevekinney.com, morphllm.com.

---
description: "TypeScript type safety and strict-mode conventions"
globs: ["**/*.ts", "**/*.tsx", "!**/*.test.ts"]
alwaysApply: false
---

# TypeScript Conventions

- No `any` — use `unknown` and narrow it
- Explicit return types on public functions
- `satisfies` operator over `as` for literal validation
- No non-null assertions (`!`) — handle null explicitly
- Discriminated unions over boolean flags for state
- `type` for unions/intersections; `interface` for object shapes
- Avoid enums — use `as const` object + `typeof` extraction
- Zod for runtime validation at system boundaries

## Version: TypeScript 5.4+
- `NoInfer<T>` utility type is available

6. API scaffold template — Manual

No description, no globs. Invoked with @api-scaffold when you want the agent to scaffold a new route with your conventions. Pattern from sitepoint.com.

---
alwaysApply: false
---

# New API Route Template

Invoke with `@api-scaffold` before asking Cursor to create a new route.

1. Create route.ts at the correct src/app/api/ path segment
2. Import shared response helpers from @/lib/api-response
3. Validate request body with Zod schemas from src/schemas/
4. Use the db singleton from @/lib/db — never instantiate Drizzle directly
5. Return `{ data: T }` on success, `{ error: string, code: string }` on failure
6. Add a corresponding .test.ts file at the same path

Community consensus from techsy.io and morphllm.com: five to eight rules is the practical sweet spot. One always-apply base rule, three to four auto-attached by file type, one or two manual templates. Beyond that you’re fighting context limits. For deeper Cursor integration with MCP servers and external tools, see how to build custom MCP tools for Cursor.

Some teams prefix files numerically (001-base.mdc, 020-react.mdc) to control merge order. Not official — Cursor string-sorts the filenames — but it works.

Five anti-patterns that silently break rules

1. Overloading alwaysApply

Every always-apply rule is in every prompt, unconditionally. morphllm.com measured 8–12% of context consumed by rules before any code is read when multiple large rules carry alwaysApply: true.

With too many always-on rules, the model tries to satisfy all of them simultaneously and partially violates most, prioritizing rules earlier in the merged set.

Limit: keep total always-apply content under ~2,000 tokens. One or two short universal rules. Everything else gets scoped by glob or description.

2. .cursorrules in Agent mode

Covered above. The short version: 0/9 vs 9/9 compliance in documented testing. No warning. Migrate.

3. Session-length degradation

Rules followed at session start drift after 10–15 conversation turns. Early context gets deprioritized as the session grows. dev.to/vibestackdev and multiple forum threads document this.

The fix: start fresh sessions for new tasks. Use glob-scoped Auto Attached rules rather than always-apply where possible — they’re re-injected per request and don’t degrade the same way.

4. Disabled Team Rules still loading (confirmed bug, May 2026)

Cursor Enterprise lets teams publish shared rules that individual users can toggle on or off. Toggling them off doesn’t work. Disabled rules still appear in agent context and are reported as “always applied workspace rules.”

Cursor engineering acknowledged the bug on May 18, 2026: “the fix that shipped in 3.4.12 was incomplete. Your disabled rules are still being injected into the agent context through a separate server-side path.” Status: unresolved as of June 2026 (confirmed on 3.4.20).

Workaround: delete optional team rules entirely rather than toggling them.

5. YAML frontmatter syntax errors silently skip files

Any YAML error causes Cursor to skip the entire .mdc file — no error logged, no warning. The rule simply doesn’t exist. dev.to/vibestackdev documents the three most common forms:

# WRONG — glob not wrapped in array
globs: "src/components/**/*.tsx"

# WRONG — boolean capitalized (YAML is case-sensitive)
alwaysApply: True

# WRONG — opening --- without closing ---
---
description: "My rule"
alwaysApply: false
(file continues with no closing delimiter)

Correct form:

---
description: "My rule"
globs: ["src/components/**/*.tsx"]
alwaysApply: false
---

Add a pre-commit hook that runs a YAML linter on .cursor/rules/*.mdc files. The cost of a CI check is low; the cost of a silently broken rule is a day of debugging model behavior.

Rule vs. one-off system prompt

If you’d type the same instruction again next week for the same task, make it a rule. If it’s context-specific to one session — “treat all async functions as fire-and-forget for this refactor” — put it in chat and forget it.

The test: would a new team member need to know this for every Cursor session, or just for today’s task?

What you should do now

  1. Check whether you have a .cursorrules file. If you do and you use Agent mode, migrate it today.
  2. Audit your existing rules for alwaysApply: true. If more than two rules carry that flag, convert the others to glob-scoped or description-based.
  3. Validate your .mdc frontmatter with a YAML linter. Add it as a pre-commit hook.
  4. If you’re on Cursor Enterprise and disabled team rules: delete them rather than toggling.

Cursor keeps shipping fast. The rule system has changed meaningfully across minor versions, and the .cursorrules silent-failure bug only became widely understood when community testing made it explicit. Treat your rules as code — version them, review them, and validate them.


References