Skip to content
claudewheel.mv
Edit
On this page

Move session data after a project directory rename.

#claudewheel.mv

#claudewheel.mv

Move session data after a project directory rename.

#MvResult

Counters tracking the outcome of a project-directory move operation.

#_discover_profile_dirs

python
def _discover_profile_dirs(ws: 'Workspace') -> list[Path]

Find all profile directories plus ~/.claudewheel/shared/ if it exists.

Enumerates profiles via the workspace's ProfileStore, then includes the shared store directory as a peer target (it holds the actual session data). A corrupt token entry raises TokenStoreError -- the uniform hard-error contract.

#_rewrite_jsonl_file

python
def _rewrite_jsonl_file(path: Path, old_path: str, new_path: str, dry_run: bool) -> int

Replace old_path with new_path in every line of a JSONL file.

Returns the number of lines where a replacement was made.

#_plan_migrations

python
def _plan_migrations(old_resolved: str, new_resolved: str, descendants: set[str]) -> list[tuple[str, str]]

Build the ordered (old, new) migration plan.

Includes old_resolved itself. Every destination is new_resolved plus the source's relative suffix. Longest old paths come first so a shorter prefix is never processed before its own descendants (prefix-shadowing prevention, same pattern as import_'s rewriters).

#_decode_rel

python
def _decode_rel(root: Path, enc: str) -> list[str]

Find every existing relative dir path under root whose encoding is enc.

The path encoding is lossy ('/', '.', '_' and literal '-' all become '-'), so one encoded string can correspond to several real paths. All matches are returned so the caller can detect ambiguity.

#_read_claude_json

python
def _read_claude_json(path: Path) -> dict[str, Any]

Read one profile's .claude.json, hard-erroring when it cannot be read.

Swallowing an unreadable registry is not an option here: during discovery it turns decodable descendants into spurious "undecodable orphan" errors that name the wrong cause, and during the update pass it lets the migration report success while leaving that profile's projects{} at the old path. Both readers refuse instead, per the module's uniform hard-error contract.

#_collect_project_keys

python
def _collect_project_keys(profile_dirs: list[Path], shared_dir: Path) -> set[str]

All real-path keys under projects{} across every profile's .claude.json.

An unreadable or malformed .claude.json is a hard error (see _read_claude_json), not a profile silently contributing zero keys.

#_discover_descendants

python
def _discover_descendants(profile_dirs: list[Path], old_resolved: str, source_root: Path, known_keys: set[str]) -> set[str]

Every real project path equal to or under old_resolved that has data.

Union of (a) .claude.json projects{} keys under OLD and (b) encoded projects/ dir names that decode to a path under OLD. Encoded names are never prefix-matched directly -- the encoding is ambiguous -- so each candidate is resolved back to a real path via the known keys plus filesystem checks under the moved tree. A candidate that resolves to a sibling path (merely sharing the encoded prefix) is skipped; one that cannot be resolved to exactly one real path is a hard error.

#_verify_destinations

python
def _verify_destinations(migrations: list[tuple[str, str]], old_resolved: str, source_root: Path, new_resolved: str) -> None

Hard-error unless every descendant's destination will exist on disk.

source_root is the moved tree as it currently exists (OLD before the rename, NEW in post-hoc mode), so source_root/<suffix> existing now is equivalent to NEW/<suffix> existing at migration time. On failure, every unresolvable descendant is listed and nothing is migrated.

#_rename_project_dir

python
def _rename_project_dir(old_project: Path, new_project: Path, dry_run: bool) -> bool

Rename old_project to new_project, merging when the target exists.

Returns True when a rename or merge happened (or would happen in dry run).

#_rewrite_prefixed_path

python
def _rewrite_prefixed_path(path: str, migrations: list[tuple[str, str]]) -> str

Rewrite a real path equal to or under a migrated source path.

migrations is longest-source-first, so the most specific mapping wins.

#_update_claude_json

python
def _update_claude_json(path: Path, migrations: list[tuple[str, str]], dry_run: bool) -> tuple[int, int]

Rename project keys and rewrite githubRepoPaths in one .claude.json.

Project keys live under data["projects"]; every key matching a migration source is renamed to its destination. githubRepoPaths values (repo -> list of local paths) equal to or under a migration source are rewritten too. Returns (project_keys_updated, github_paths_updated).

An unreadable or malformed file is a hard error (see _read_claude_json): returning (0, 0) would let the migration report success while this profile's registry still points at the old path.

#run_mv

python
def run_mv(ws: 'Workspace', old_path: str, new_path: str, dry_run: bool=False, quiet: bool=False, post_hoc: bool=False) -> MvResult

Rename a project directory and migrate Claude Code session data.

In default mode, renames old_path to new_path on the filesystem and then migrates all session data. With post_hoc=True, skips the filesystem rename (the directory was already renamed externally) and only migrates sessions.

The migration is prefix-aware: every project keyed at old_path or nested under it (Claude Code projects inside the moved tree) is migrated to new_path plus the same relative suffix. That covers the encoded projects/ dirs, the projects{} keys and githubRepoPaths entries in every profile's .claude.json, and the JSONL cwd references of every migrated project. Each descendant's destination must exist on disk under new_path; otherwise the operation aborts before touching anything.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search