Move folders while preserving Cursor workspace metadata and chat history
Project description
cursor-move
cursor-move moves or renames folders while preserving the Cursor
workspace metadata and local chat history associated with their old paths.
[!CAUTION] This is an independent, alpha-quality utility that edits Cursor's private storage format. It is not affiliated with or supported by Cursor or Anysphere. Always inspect a dry run and keep the generated backup until the migrated workspaces have been verified.
Why this exists
Cursor identifies a workspace using both its absolute path and filesystem birth
time. Moving a project in Finder or with mv changes the path but does not
update Cursor's related records. The result can be missing chat history, empty
workspace state, or duplicate projects.
cursor-move performs the filesystem move and updates the relevant local
metadata as one guarded workflow. Cursor's User directory is discovered at:
| Platform | Default User directory |
|---|---|
| macOS | ~/Library/Application Support/Cursor/User |
| Linux | $XDG_CONFIG_HOME/Cursor/User or ~/.config/Cursor/User |
| Windows | %APPDATA%\Cursor\User |
Within it, the tool migrates workspaceStorage, globalStorage/storage.json,
globalStorage/state.vscdb, workspace-local state databases, and caches. It
also migrates ~/.cursor/projects when that directory exists.
All processing is local. The runtime has no network dependencies and does not upload workspace data or chat content.
Requirements
- macOS, Linux, or Windows
- Python 3.12 or newer
- A local Cursor installation with an initialized
Userdata directory - Enough free disk space for one complete global-state database backup plus a safety margin
Cursor must be fully closed for a real migration. The command checks this before planning any changes and exits if Cursor is running or the process list cannot be inspected safely. After the check passes, do not open Cursor until the command reports that the migration is complete.
Installation
Install directly from a checkout with uv:
uv tool install .
Or run it without installing:
uv run cursor-move --help
The project name cursor-move was available on PyPI when this repository was
prepared, but no PyPI release is assumed by these instructions.
Usage
Always start with --dry-run. Dry runs may be performed while Cursor is open
because they do not change files or databases.
Move one folder
cursor-move --dry-run '/work/old-project' '/archive/new-project'
After reviewing the plan, quit Cursor completely and run:
cursor-move '/work/old-project' '/archive/new-project'
DESTINATION is the exact new path, not merely its parent directory.
Move several folders with a regex
With --regex, SOURCE is matched against complete absolute directory paths.
The destination may reference numbered or named capture groups using \1,
\g<name>, $1, or ${name}.
cursor-move --dry-run --regex \
'^/Users/me/Projects/(client-.+)$' \
'/Users/me/Archive/$1'
The search root is inferred from the literal prefix of SOURCE. If it cannot
be inferred safely, specify it explicitly:
cursor-move --dry-run --regex --scan-root '/Users/me/Projects' \
'^/Users/me/Projects/(client-.+)$' \
'/Users/me/Archive/$1'
Regexes always see absolute paths with forward slashes. On Windows, use that canonical form even though ordinary exact paths may use native backslashes:
cursor-move --dry-run --regex `
'^C:/Users/me/Projects/(client-.+)$' `
'C:/Users/me/Archive/$1'
Matched folders are treated as move units, so their descendants are not also matched. The command rejects duplicate destinations, existing destinations, and attempts to move a directory inside itself before moving anything.
Repair metadata after a completed move
If a folder was already moved but Cursor metadata was not updated, pass its old
and current paths with --metadata-only:
cursor-move --metadata-only \
'/work/old-project' \
'/archive/new-project'
--already-moved is retained as an alias. Metadata-only repair currently
accepts exact paths rather than regex transformations.
Portable and custom installations
Override auto-detection when Cursor uses a custom or portable data directory:
cursor-move --cursor-user-dir '/custom/Cursor/User' \
'/work/old-project' '/work/new-project'
Use --cursor-projects-dir when .cursor/projects also lives elsewhere.
Safety and recovery
Before changing the global SQLite database, cursor-move:
- refuses to run while Cursor is open;
- checks that enough free disk space is available;
- uses SQLite's online backup API, including committed WAL data;
- runs
PRAGMA quick_checkon the backup; and - aborts before modifying the source database if backup validation fails.
Validated backups are created beside the database with names such as:
state.vscdb.bak-1785330644
Keep the newest backup until Cursor opens normally and the expected workspaces
and chats are visible. To recover, fully quit Cursor, preserve the active
state.vscdb, state.vscdb-wal, and state.vscdb-shm together, then restore a
validated backup without reusing stale WAL/SHM files.
Cursor databases and logs may contain project paths, workspace data, account details, and chat content. Never attach them to a public issue without careful redaction.
Limitations
- Cursor's internal schema is private and may change without notice.
- Multi-root
.code-workspacemetadata has not been comprehensively tested. - The operation spans the filesystem and several independent metadata stores; it cannot be fully atomic across all of them.
- The startup check cannot stop someone from launching Cursor midway through a long migration.
- Regex metadata-only recovery is not currently supported.
- Windows drive/UNC paths and Linux inode-based workspace IDs are implemented, but destructive migrations should still begin with a dry run and a retained backup on every new Cursor release.
- WSL, SSH, containers, network shares, and portable installations can place the GUI metadata somewhere other than the project filesystem; use the data directory belonging to the machine that runs the Cursor GUI.
Troubleshooting
“Cursor is running, or its process state could not be checked safely”
Fully quit Cursor. On macOS use Cmd+Q; on Windows ensure Cursor.exe is gone;
on Linux ensure the Cursor binary or AppImage has exited. The check deliberately
fails closed when pgrep or PowerShell cannot inspect processes.
“Not enough free disk space for a safe SQLite backup”
Free the amount reported by the command. Do not remove the only known-good backup to make room unless another validated copy exists.
The folders moved but metadata migration failed
Keep Cursor closed and rerun the affected exact path with --metadata-only.
Do not use a stale global database together with newer -wal or -shm files.
Development
See CONTRIBUTING.md for setup and test commands and docs/design.md for storage details and failure invariants. Platform behavior and test boundaries are documented in docs/platforms.md.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cursor_move-0.1.0.tar.gz.
File metadata
- Download URL: cursor_move-0.1.0.tar.gz
- Upload date:
- Size: 34.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
098b874a166f88a8229990772b54b020bf2a53fca6e64afbbfae570da3dc2248
|
|
| MD5 |
8a19b5ad041d432d379316ffda841537
|
|
| BLAKE2b-256 |
cd5368ddc47156a4074c6babd46aa0e9efece06a73cc28b974e853cbddf7a3bb
|
Provenance
The following attestation bundles were made for cursor_move-0.1.0.tar.gz:
Publisher:
release.yml on NRB-Tech/cursor-move
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cursor_move-0.1.0.tar.gz -
Subject digest:
098b874a166f88a8229990772b54b020bf2a53fca6e64afbbfae570da3dc2248 - Sigstore transparency entry: 2300688073
- Sigstore integration time:
-
Permalink:
NRB-Tech/cursor-move@3ec8df81e34454cfbe66dd735bf90cdc9cd6907e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/NRB-Tech
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3ec8df81e34454cfbe66dd735bf90cdc9cd6907e -
Trigger Event:
release
-
Statement type:
File details
Details for the file cursor_move-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cursor_move-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ccce95a13fddd39ec7e028734b586f57895724129d44b24eaf8997b943451d83
|
|
| MD5 |
511862cc8e65daae6f77c01384a0bcb1
|
|
| BLAKE2b-256 |
ce23d01687214aa21d48fbee6c39089fbc32ee1777e23c3f16cbe25829f4a0a1
|
Provenance
The following attestation bundles were made for cursor_move-0.1.0-py3-none-any.whl:
Publisher:
release.yml on NRB-Tech/cursor-move
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cursor_move-0.1.0-py3-none-any.whl -
Subject digest:
ccce95a13fddd39ec7e028734b586f57895724129d44b24eaf8997b943451d83 - Sigstore transparency entry: 2300688099
- Sigstore integration time:
-
Permalink:
NRB-Tech/cursor-move@3ec8df81e34454cfbe66dd735bf90cdc9cd6907e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/NRB-Tech
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3ec8df81e34454cfbe66dd735bf90cdc9cd6907e -
Trigger Event:
release
-
Statement type: