All articles
tips and-tricks·intermediate··Updated

Fix broken project paths in Claude Code and Codex

A macOS migration script discovers stale project paths in four Claude Code and Codex sources, then updates the related local state with explicit safety limits.

automationclaude-codeclicodexdeveloper-experiencetooling
Resources

The problem

Claude Code and Codex can retain a project path in several local locations. Moving the directory does not rewrite those references.

Session history vanishes. Thread-to-directory associations break. Project-level configuration points to directories that no longer exist. The tools silently lose track of projects, and the only visible symptom is that your history is gone.

The locations the script updates:

Where project paths are stored
Store Location What breaks
Claude Code ~/.claude/projects/<encoded-path>/ Session history, memory, project settings
Claude Code ~/.claude/projects/*/.session-aliases Session aliases that point at the encoded directory
Claude Code ~/.claude.json (projects dict) Project-level configuration
Claude Code ~/.claude.json (githubRepoPaths) GitHub-to-local-path mapping
Codex ~/.codex/state_5.sqlite Thread-to-CWD associations
Codex ~/.codex/config.toml [projects."path"] sections
Codex ~/.codex/.codex-global-state.json Workspace root references
Codex ~/.codex/sessions/*/rollout-*.jsonl Per-session cwd fields

The state spans encoded directory names, JSON, TOML, SQLite, and JSONL. A manual migration must preserve the same old-to-new mapping across every location that contains it.


What migrate-project does

migrate-project is a bash script with two different scopes. Four scanners discover stale roots in Claude project directories, .claude.json, Codex’s SQLite database, and config.toml. Once a mapping is selected, the migration also rewrites Codex global state and rollout files.

Install

terminalbash
curl -fsSL -o ~/.local/bin/migrate-project \
  https://gist.githubusercontent.com/ravikanchikare/613ec7934b4c5736acea2914dfccd067/raw/migrate-project.sh
chmod +x ~/.local/bin/migrate-project

The script targets macOS and requires bash, sqlite3, python3, awk, grep, and BSD sed. It also assumes ~/.local/bin is on $PATH.

Commands

See what is broken:

terminalbash
migrate-project list

Outputs a table of broken paths, their suggested new locations, which stores reference them, and how many threads are associated.

Detailed view with suggestions:

terminalbash
migrate-project discover

Same data, presented as a readable report with confidence levels for each suggestion.

Interactively fix each broken path:

terminalbash
migrate-project walk

Walks through each broken path one at a time. For each one, it shows the suggested new location and offers accept, manual entry, ignore, or skip. Flags must precede the command:

terminalbash
migrate-project --dry-run walk

One-off explicit migration:

terminalbash
migrate-project /old/project/path /new/project/path

Migrates a single path directly, without interactive prompting.


How it works

The script operates in three phases:

Discovery

Stage 1

Four scanners inspect Claude project directories, .claude.json, the Codex threads database, and config.toml. Each missing directory becomes a pipe-delimited record (path|source|thread_count), then duplicate paths are merged.

Suggestion

Stage 2

For each broken path, the script extracts the basename and checks five parent directories (~/Documents, ~/code, ~/workspace, ~/Desktop, ~). One match becomes an auto-suggestion; zero matches means only that no candidate exists in those locations.

Migration

Stage 3

When a mapping is confirmed, the script renames the encoded directory and updates session aliases, runs UPDATE on state_5.sqlite, uses sed for config.toml, global state, and rollout files, and uses python3 for .claude.json.

Discovery in detail

The four scanners each target a different storage format:

scan_claude_projects reads ~/.claude/projects/ directory names. Since Claude Code encodes paths by replacing / with -, decoding is lossy — a directory named -Users-ravi-code-my-app could only be decoded by reversing the substitution, but hyphens in original path components create ambiguity. The scanner uses .claude.json as an authoritative lookup table (encoded name to real path), falling back to naive decode only when no mapping exists. Worktree directories are explicitly skipped.

scan_codex_sqlite runs a SQL query against state_5.sqlite to pull every distinct cwd value from the threads table, along with a count of how many threads reference each path. It checks each one against the filesystem.

scan_codex_config uses grep to extract paths from [projects."<path>"] section headers in config.toml.

scan_claude_json iterates over the projects dict keys in .claude.json, skipping worktree entries, and tests each key against the filesystem.

Results from all four scanners are merged through an awk-based deduplicator that combines source labels and sums thread counts for paths found in multiple stores.

Migration in detail

When a mapping is confirmed, run_migration calls three functions:

migrate_claude renames the encoded directory under ~/.claude/projects/ from the old encoded name to the new one. It also walks every .session-aliases file in other project directories and updates any alias pointing to the old directory.

migrate_codex handles four separate stores. SQLite gets an UPDATE threads SET cwd = 'new' WHERE cwd = 'old'. config.toml gets sed replacements for both the section header and any source = "old..." lines. .codex-global-state.json gets a global sed replacement. Session rollout files get matched via grep and patched individually.

migrate_claude_json uses python3 to read and rewrite .claude.json — moving the project key from old to new, and updating githubRepoPaths entries that reference the old path.

The backup policy is partial. state_5.sqlite, config.toml, and .codex-global-state.json receive .bak copies before modification. The script does not back up the renamed Claude project directory, .claude.json, .session-aliases, or changed rollout JSONL files.


Design decisions

Keep the implementation inspectable. One shell file exposes every scanner and mutation path. That makes review easier, but it does not remove dependencies: python3 is not guaranteed on a stock macOS installation, and the sed -i '' calls are macOS-specific.

No stale-entry cleanup command. The walk command offers accept, manual, ignore, skip, and quit. It cannot delete an abandoned mapping. During a migration, however, migrate_claude removes the old encoded directory when the target encoded directory already exists.

Lossy encoding requires an external source of truth. Claude Code encodes paths by replacing / with -. Since hyphens are legal in directory names, the encoding cannot be uniquely reversed. The script relies on .claude.json as the authoritative mapping. If .claude.json is missing or corrupted, the naive decode fallback may produce incorrect paths for directories with hyphens in their names.

There is no complete automatic undo. The three Codex .bak files can restore those stores. A full rollback also requires the separate backup created before running the script.

Worktrees are explicitly excluded. Worktree directories under ~/.claude/projects/ contain encoded paths with --claude-worktrees segments. These are temporary by nature and are skipped during discovery to avoid false positives.



Takeaways

Discovery and migration cover different surfaces

Four scanners find stale roots. A confirmed mapping also updates session aliases, Codex global state, and rollout files that discovery does not scan independently.

Discovery is the hard part

The migration logic is straightforward. Finding every broken reference across SQLite databases, JSON configs, TOML files, and encoded directory names — that is where the complexity lives.

Lossy encoding creates real constraints

Claude Code encodes paths by replacing slashes with hyphens, which means directories with hyphens in their names cannot be decoded unambiguously. An external source of truth (.claude.json) is necessary to resolve them.

Small scripts still have prerequisites

The implementation depends on bash, sqlite3, python3, awk, grep, and macOS-style sed. Check those commands before running it on a new machine.

Backup coverage is partial

The script backs up the Codex database, config, and global-state file. Claude project directories, .claude.json, and rollout JSONL files need a separate backup.