Stop Symlinking Your Cursor and Claude Code Rules — Generate Them Instead A developer building with both Cursor and Claude Code discovered that symlinking their rules directories fails silently due to incompatible file extensions and frontmatter schemas. They built a zero-dependency Node.js code generator that keeps both formats in sync from a single source of truth in `.claude/rules/*.md`. If you use both Cursor and Claude Code on the same repo, you have probably noticed both tools support a rules/ directory of persistent instructions. The obvious move is to make them share one directory with a symlink. Don't. The two formats overlap just enough to look compatible and just differently enough to fail silently. This post walks through the exact incompatibilities and a small code generator that keeps both formats in sync from a single source of truth. Both tools let you drop markdown files with YAML frontmatter into a project directory. Instructions apply either always or scoped to file globs. Cursor — .cursor/rules/ .mdc : --- description: "Run ESLint after changing TS/JS" globs: extensions/app/ / .{ts,tsx,js} alwaysApply: false --- ESLint after edits Run npm run lint before considering the task done. Cursor derives the rule type from a combination of three fields: alwaysApply | description | globs | Type | |---|---|---|---| true | — | — | Always | false | — | provided | Auto Attached | false | provided | omitted | Agent Requested | false | omitted | omitted | Manual @-only | Claude Code — .claude/rules/ .md : --- description: Run ESLint after changing TS/JS paths: - "extensions/app/ / .{ts,tsx,js}" --- ESLint after edits Run npm run lint before considering the task done. Claude Code's model is simpler: a rule with a paths: list loads only when Claude reads a matching file; a rule with no paths: loads unconditionally every session same priority as .claude/CLAUDE.md . There is no "agent decides from description" mode. The tempting shortcut: ln -s ../.claude/rules .cursor/rules One physical directory, both tools point at it, done. Except it breaks on two independent axes: 1. File extension. Cursor's rule loader only reads .mdc and ignores plain .md . Claude Code discovers .md and ignores .mdc . A single file on disk cannot have both extensions, so whichever you pick, exactly one tool silently sees zero rules. No error, no warning — the rules just don't apply. 2. Frontmatter keys. Even if you dodged the extension problem, the scoping keys differ: globs: as a alwaysApply: . paths: as a alwaysApply .Point Claude Code at a Cursor-format file and it sees no paths: key, so it treats every rule as always-on — your carefully scoped API rule now loads on every session. The failure is invisible because the file is being read; it's just being interpreted with the wrong schema. This is the trap that motivated the whole exercise: I had .cursor/rules - ../.claude/rules and "fixed" the files to Claude Code's format, which instantly broke Cursor without a single error message. Since the bodies are identical and only the frontmatter differs, treat one format as the source of truth and generate the other. I picked .claude/rules/ .md as the source Claude Code reads it directly and generate .cursor/rules/ .mdc . The mapping is mechanical: Source .md | Generated .mdc | |---|---| paths: list present | globs: