Skip to content

fix(sync): stop syncCursor deleting the user's .cursor/ state#36

Merged
MANVENDRA-github merged 1 commit into
mainfrom
fix/cursor-wipe-what-you-own
Jul 8, 2026
Merged

fix(sync): stop syncCursor deleting the user's .cursor/ state#36
MANVENDRA-github merged 1 commit into
mainfrom
fix/cursor-wipe-what-you-own

Conversation

@MANVENDRA-github

Copy link
Copy Markdown
Owner

The bug

syncCursor called rmGenerated on the whole .cursor/ tree, while syncClaude, syncCodex, and syncOpenCode each remove only the subdirectories they generate.

So npm run sync deleted every file a user keeps in .cursor/ — including .cursor/environment.json, Cursor's own background-agent config.

Demonstrated by planting files and running sync, before the fix:

.claude/settings.local.json   SURVIVED
.claude/other/user.txt        SURVIVED
.cursor/environment.json      DELETED     ← Cursor's own config
.cursor/my-own-rule.mdc       DELETED

The README already promised the opposite:

An adapter deletes only the subdirectories it generates … never the parent .claude/.

And syncCursor's own docstring said agents get an agentry- prefix "to avoid collisions with the user's own Cursor agents" — agents the same function had just deleted three lines earlier. The prefix only ever protected the install path.

The guard had drifted the same way

protect-generated-dirs listed .cursor/ and .codex/ wholesale, so it blocked edits to:

  • .cursor/environment.json — which sync (after this fix) preserves
  • .codex/config.toml — which sync always preserved, and which the README names as Codex's per-user state

A guard that refuses an edit the engine would have kept is as wrong as one that permits an edit the engine will destroy.

The fix

syncCursor wipes .cursor/agents/ and .cursor/rules/ only. .cursor/mcp.json joins .mcp.json and opencode.json under D20 — written when MCP sources exist, never deleted, may carry the user's own servers.

The hook's GENERATED list now names the wiped subdirectories exactly:

".claude/agents/", ".claude/skills/", ".claude/commands/", ".claude/rules/", ".claude/hooks/",
".claude-plugin/",
".cursor/agents/", ".cursor/rules/",
".codex/agents/skills/",
".opencode/agents/", ".opencode/skills/", ".opencode/commands/",

Verification — the hook and the engine must describe the same set

Planted a file at all 18 harness paths, ran sync, recorded what survived, and compared it against the hook's exit code. Correct pairing is wiped → BLOCK, preserved → allow.

path                                   sync       hook     agree?
.claude/agents/zz-user.md              wiped      BLOCK    yes
.claude/skills/zz/SKILL.md             wiped      BLOCK    yes
.claude/rules/zz.md                    wiped      BLOCK    yes
.claude/hooks/zz.js                    wiped      BLOCK    yes
.claude/settings.local.json            preserved  allow    yes
.claude/zz-user.json                   preserved  allow    yes
.cursor/agents/zz.md                   wiped      BLOCK    yes
.cursor/rules/zz.mdc                   wiped      BLOCK    yes
.cursor/environment.json               preserved  allow    yes
.cursor/mcp.json                       preserved  allow    yes
.codex/agents/skills/zz/SKILL.md       wiped      BLOCK    yes
.codex/config.toml                     preserved  allow    yes
.opencode/agents/zz.md                 wiped      BLOCK    yes
.opencode/skills/zz/SKILL.md           wiped      BLOCK    yes
.opencode/commands/zz.md               wiped      BLOCK    yes
.opencode/zz-user.json                 preserved  allow    yes
.mcp.json                              preserved  allow    yes
opencode.json                          preserved  allow    yes

mismatches: 0

Plus the three properties the change could have broken:

  • Generated output byte-identicalgit diff --numstat -- .cursor0 files
  • Still idempotent — two consecutive syncs hash identically
  • Stale removal still works — deleting skills/caching/ and re-syncing removes .cursor/rules/caching.mdc
$ npm run lint   # PASS
$ npm test       # 243 pass, 0 fail   (was 239)
$ npm run sync   # clean, no drift

README's per-user-state list gains Cursor — it had none to name while sync was deleting it.

syncCursor called rmGenerated on the whole .cursor/ tree, while syncClaude,
syncCodex, and syncOpenCode each remove only the subdirectories they generate.
So `npm run sync` deleted every file a user keeps in .cursor/ -- including
.cursor/environment.json, Cursor's own background-agent config.

Demonstrated before the fix by planting files and running sync:

  .claude/settings.local.json   SURVIVED
  .claude/other/user.txt        SURVIVED
  .cursor/environment.json      DELETED
  .cursor/my-own-rule.mdc       DELETED

The function's own docstring said agents are prefixed `agentry-` "to avoid
collisions with the user's own Cursor agents" -- agents the same function had
just deleted. The prefix only ever protected the install path.

Now wipes .cursor/agents/ and .cursor/rules/ only. .cursor/mcp.json joins
.mcp.json and opencode.json under D20: written when MCP sources exist, never
deleted, may carry the user's own servers.

The guard had drifted from the engine the same way. protect-generated-dirs
listed `.cursor/` and `.codex/` wholesale, so it blocked edits to
.cursor/environment.json and .codex/config.toml -- two files sync preserves,
and the second is the file README names as Codex's per-user state. Both are
narrowed to the wiped subdirectories, so the guard and the engine now describe
the same set.

Verified by planting a file at all 18 harness paths, syncing, and comparing
what survived against the hook's verdict: 18/18 agree, every wiped path blocks
and every preserved path allows. Generated output is byte-identical, sync is
still idempotent across two runs, and deleting a source still removes its stale
.cursor/rules/*.mdc.

Suite: 239 -> 243 tests. README's per-user-state list gains Cursor, which had
none to name while sync was deleting it.
@MANVENDRA-github
MANVENDRA-github merged commit 5ddff8d into main Jul 8, 2026
3 checks passed
@MANVENDRA-github
MANVENDRA-github deleted the fix/cursor-wipe-what-you-own branch July 8, 2026 19:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant