A Cursor Agent Skill that migrates your Cursor chat history, agent transcripts, and Agents sidebar when a project folder moves to a new path (macOS).
Copies a short prompt — paste it into Cursor (or another AI agent) to set this up for you.
Migrate Cursor history only when a project folder changes path. Do not delete or modify the source folder unless the user explicitly asks for a fresh reset.
All paths come from the user via --old / --new (or --source / --dest for fresh reset). Never assume fixed roots like OneDrive or ~/personal-dev — always ask or use the paths the user provides.
This is a Cursor Agent Skill. Create it at
~/.cursor/skills/cursor-folder-migrate/SKILL.md(with the companionscripts/migrate.py) and invoke it when a project folder moves.
pgrep -x Cursor must be empty).| Store | Path | Holds |
|---|---|---|
| Agent transcripts | ~/.cursor/projects/<sanitized-path>/ | JSONL transcripts, MCP configs, assets |
| Workspace chat DB | ~/Library/Application Support/Cursor/User/workspaceStorage/<hash>/state.vscdb | Open tabs (composer.composerData) |
| Agents sidebar | ~/Library/Application Support/Cursor/User/globalStorage/state.vscdb | composerHeaders table + composer.composerHeaders ItemTable cache |
Do not broadly edit globalStorage/state.vscdb beyond composerHeaders / allComposers. Chat message bodies live in cursorDiskKV (bubbleId:<composerId>:*) and are keyed by composer ID — they move with the agent entries, not the workspace path.
Cursor/VS Code does not use md5("file://" + path).
hash = md5(absolute_fs_path + str(birthtime_ms))
No comments yet.
birthtime_ms = int(os.stat(path).st_birthtime * 1000)
A re-copy or re-sync that changes birthtime → new hash → history orphaned unless remigrated.
~/.cursor/projects/ folder name = absolute path with leading / removed, / → -:
/path/to/old/project → path-to-old-project
Example: /Users/you/projects/my-app → Users-you-projects-my-app
workspaceStorage/*/workspace.json for entries matching OLD or NEW. The computed hash may differ from where history actually lives (folder opened under a slightly different path, or birthtime changed after re-copy). Override with --old-hash / --new-hash if needed.globalStorage/state.vscdb) before any writes.cp -R ~/.cursor/projects/<OLD_PROJ> ~/.cursor/projects/<NEW_PROJ>.workspaceStorage/<OLD_HASH>/state.vscdb* → <NEW_HASH>/, and write workspace.json = {"folder": "file:///absolute/new/path"}.composerHeaders table (workspaceId, workspaceIdentifier.id, workspaceIdentifier.uri.fsPath) and the ItemTable key composer.composerHeaders (allComposers[]). Missing the second step → tabs work but the sidebar stays empty.ItemTable values (git roots, file refs).allComposers point at NEW_HASH + NEW_PATH; transcript count matches; named agents have bubbleId rows in cursorDiskKV.Tell the user to open NEW_PATH only, then Developer: Reload Window if the sidebar is empty.
When the user already opened the new path and empty workspace hashes exist: export agents from existing hashes, delete the new-path folder + its workspaceStorage hashes + ~/.cursor/projects/<new-sanitized>/, remove the stale entries from global composerHeaders + allComposers, re-copy the project (excluding node_modules, .next, cdk.out, etc.), then re-run the migration (new birthtime → new hash). npm install if code was re-copied.
| Symptom | Cause | Fix |
|---|---|---|
| Sidebar empty, tabs work | allComposers cache still on OLD_HASH | Update ItemTable composer.composerHeaders |
| Wrong agent names mixed in | Orphan-hash agents merged blindly | Filter by workspaceIdentifier.uri.fsPath; dedupe by composerId |
| History gone after re-sync | Folder birthtime changed | Pick the most-recently-touched workspace hash for the folder URI |
| Cursor opens wrong hash | Computed hash ≠ hash Cursor last wrote | Auto-select the active hash, or pass --new-hash |
| Opening old + new paths splits history | Two workspace hashes active | Open one path only |
globalStorage/state.vscdb before writes.workspaceStorage or .cursor/projects unless asked.