claude-code-migrate
v1.14.2
Published
Migrate AI CLI configs (Claude Code, Codex) between machines via SSH or local backup
Maintainers
Readme
ccm - Claude Code Migrate
CLI tool for syncing portable Claude Code and Codex setup between machines via SSH/SCP or local backup archives.
ccm is intentionally not a dotdir mirror. It ships the parts of an agent setup that should travel
between hosts, preserves runtime state on the target, and adapts known machine-specific Codex
settings after copy.
Versioning:
- Package uses semver (
1.2.0, etc.) - Git release tags use
vX.Y.Z(example:v1.2.0)
Providers:
claude(~/.claude)codex(~/.codex)
Shared skills are migrated from ~/.agents/skills and ~/.agents/lazy-skills when any provider is active.
What Syncs
ccm push uses overlay semantics: it overwrites/adds selected portable config on the target host,
but does not delete remote-only runtime state such as auth, sessions, logs, history, caches, and
local databases.
Portable surfaces today:
- Claude:
CLAUDE.md,settings.json,agents/, nativeskills/, hooks/statusline/keybindings when present, and selected MCP config from~/.claude.json - Codex:
config.toml,AGENTS.md,agents/,rules/,skills/exceptskills/.system, and local Codex marketplace sources referenced fromconfig.toml - Shared agents:
~/.agents/skills/,~/.agents/lazy-skills/, and~/.agents/.skill-lock.json
Runtime/local state is not migrated broadly. For example, auth.json, sessions, logs, SQLite state,
plugin data, and local caches are preserved on the target.
After pushing Codex, ccm normalizes path-based Codex MCP command values when the same binary is
available on the remote PATH.
Host-Aware Codex Plugins
Codex plugin enablement is host-aware. ccm copies portable marketplace sources, probes the target
host, evaluates enabled plugins against policy, and installs missing allowed plugins through the
remote codex plugin add command.
Built-in policy keeps clearly platform-specific plugins off incompatible hosts:
| Plugin | Default policy |
| --- | --- |
| build-ios-apps@openai-curated | macOS + xcodebuild |
| build-macos-apps@openai-curated | macOS + xcodebuild |
| test-android-apps@openai-curated | adb available |
| computer-use@openai-bundled | macOS GUI host |
Pushes use the target's own OpenAI runtime catalogs. Copying a bundled or primary-runtime manifest does not make its plugins available. Unavailable runtime plugins are disabled in the target config with a reason in the dry run; portable config and skills still sync. Working target runtime paths are preserved, and backups still include runtime snapshots. Custom marketplaces continue to copy.
Legacy @openai-curated IDs use @openai-curated-remote when the target confirms the same plugin
there. The host policies above apply to both names. Unknown custom plugins still require confirmed
availability. Disabled plugins are not uninstalled from the target cache.
ccm push --dry-run stays non-mutating, but for Codex it now also probes the target and previews
plugin policy/install decisions.
Installation
Install the published CLI:
bun add -g claude-code-migrate
# or
npm install -g claude-code-migrateccm runs on Bun 1.3.14 or newer on macOS and Linux. npm is a supported installation channel;
using npm install does not make the CLI compatible with the Node.js runtime.
For local development from a checkout:
bun install
bun linkUsage
Initialize config
ccm config --init
# Edit ~/.config/claude-code-migrate/config.tomlBackup
ccm backup # all enabled providers + shared
ccm backup codex # codex + shared
ccm backup codex ./out.tar.gz # codex + shared to custom path
ccm backup ./out.tar.gz # all enabled providers + shared
ccm backup --dry-runNew backups use the v2 archive manifest. It records the producing ccm version and, for every payload file, its canonical path, size, mode, and SHA-256 digest. ccm verifies the finished archive before publishing it.
Inspect and verify
ccm inspect ./ccm-backup.tar.gz # metadata summary, no destination extraction
ccm inspect ./ccm-backup.tar.gz --files # include file metadata
ccm inspect ./ccm-backup.tar.gz --json
ccm verify ./ccm-backup.tar.gz
ccm verify ./ccm-backup.tar.gz --jsoninspect exits 0 for a structurally valid v1 or v2 archive. verify exits 0 only when v2 file
integrity is verified, 1 for a valid legacy v1 archive whose integrity metadata is unavailable,
and 3 for an invalid, unreadable, or unsupported archive. JSON output follows the same exit
semantics and is suitable for automation.
Legacy v1 backups remain restorable throughout the ccm 1.x release line. To upgrade one, restore it with a trusted 1.x ccm installation, review the restored files, then create a fresh v2 backup. Do not treat that conversion as retroactive verification of the v1 archive.
Archive reads and restores use bounded streaming extraction. ccm rejects unsafe paths and entry types, duplicate or non-portable destinations, malformed manifests, integrity mismatches, and archives that exceed its compressed, expanded, per-file, entry-count, manifest, or path limits.
Successful and failed mutating restores and pushes also write private execution receipts under
${XDG_STATE_HOME:-~/.local/state}/ccm/receipts/. Use the canonical rcpt_... filename stem:
ccm receipts
ccm inspect latest
ccm verify latest
ccm inspect rcpt_0123456789abcdef0123456789abcdef
ccm inspect rcpt_0123456789abcdef0123456789abcdef --json
ccm verify rcpt_0123456789abcdef0123456789abcdef
ccm verify rcpt_0123456789abcdef0123456789abcdef --remote arda@devboxReceipt verification re-observes only the receipt's symbolic managed roots and, when applicable,
Codex plugin state, then compares fingerprints; it does not print file contents. Push receipts
use the original target when it can be matched safely from the configured target or profiles.
Otherwise, supply it through --remote; the target must match the receipt's redacted endpoint
binding. Older schema-v1 receipts remain inspectable but report drift verification as unavailable
because they do not carry an observation scope.
Doctor and diff
ccm doctor # local checks plus the configured remote target
ccm doctor --local # explicitly skip remote checks
ccm doctor --remote host # override the configured remote target
ccm diff # push diff against the configured target
ccm diff push codex host # explicit push diff
ccm diff restore ./backup.tar.gz codexRemote push planning reports observation progress in human output. JSON output remains one pure object. Observation is always fresh because an exact diff must not rely on stale managed-state data.
Push
ccm push # all enabled providers + shared (host from config)
ccm push codex # codex + shared
ccm push user@host # all enabled providers + shared
ccm push claude user@host # claude + shared
ccm push --all user@host # all providers + shared, regardless of config enablement
ccm push --providers claude,codex user@host
ccm push codex user@host --dry-run # explain settings, skills, and plugin changes
ccm push codex user@host --dry-run --verbose # every setting and changed file
ccm push codex user@host --dry-run --json # stable, redacted execution plan for automationHuman previews compare the target with the final adapted configuration. They show model and
permission changes, named MCP servers and plugins, file counts grouped by purpose, and any
blockers. Source adaptations are listed separately: for example, dropping a Mac-only MCP server
or disabling an iOS plugin on Linux. Credentials and arbitrary configuration values are hidden.
Unchanged files are summarized; --verbose expands settings and file paths. Backup, restore,
and diff use the same human presentation. JSON plans and diff schemas remain unchanged.
Live pushes summarize the payload file count, transferred and reused bytes, elapsed time, and
average payload speed. Helper uploads and raw rsync output stay quiet; use ccm push --verbose
for individual file progress and complete transfer output. Warnings and failure diagnostics
remain visible by default. A ready payload is staged data; the final success message confirms
the configuration was applied and verified.
Remote Codex commands may live in user-owned or root-owned installation directories. Group writability is accepted only when account and group records confirm the file owner's private group, including primary-group membership. Shared-group and world-writable commands remain blocked, and transaction workspaces retain their stricter private permissions.
Plugin observation scales with plugin identities rather than verbose catalog metadata. ccm
validates and deduplicates IDs on the target before transport and accepts additional CLI metadata
fields. The full catalog has a separate 32 MiB input budget and 30-second deadline; the compact
identity list retains its 1 MiB budget. Real marketplace manifests have a 32 MiB planning budget,
independent of the 4 MiB configuration-file budget. Exceeding a budget reports which limit failed,
instead of claiming SSH is broken. Hung catalog processes and their children are stopped. Plugin
recovery queries installed plugins only, so unrelated catalog growth cannot break reconciliation.
Files named marketplace.json inside skills or test fixtures are ordinary payload files; only
actual Codex marketplace entry points affect plugin availability.
When exactly one profile has the selected target host, push and push diff apply it
automatically. Use --profile <name> to select one explicitly or --no-auto-profile to push
without automatic profile selection. Multiple matching profiles fail closed.
Restore
ccm restore ./ccm-backup.tar.gz # restore all providers in archive
ccm restore ./ccm-backup.tar.gz codex # restore only codex
ccm restore ./ccm-backup.tar.gz --dry-runIf a local restore stops in a recoverable transaction, ccm transactions prints the exact
recovery commands. ccm recover --rollback and ccm recover --accept infer the transaction only
when exactly one journal matches; pass the transaction ID whenever the choice is ambiguous.
Restored Codex hook trust is deliberately removed. Review the restored hook commands and approve them again on the destination host before relying on them.
Config file
Location: ~/.config/claude-code-migrate/config.toml
[target]
type = "ssh"
host = "[email protected]"
[providers.claude]
enabled = true
settings_local = false
mcp_config = true
[providers.codex]
enabled = true
# Optional host-aware Codex plugin overrides.
# Built-in defaults already handle obvious platform-specific OpenAI plugins.
# mode: auto | always | never | preserve
#
# [providers.codex.plugin_policies."build-ios-apps@openai-curated"]
# mode = "auto"
# os = ["darwin"]
# commands = ["xcodebuild"]
#
# [providers.codex.plugin_policies."computer-use@openai-bundled"]
# mode = "never"
[backup]
path = "~/backups/ccm"Codex plugin policy fields:
mode = "auto"evaluates optional host requirements.mode = "always"explicitly requires the plugin, blocking before upload if unavailable.mode = "never"disables the plugin on the target.mode = "preserve"restores the target's previous enabled/disabled value after overlay copy.os = ["darwin" | "linux" | "windows"]restrictsautoto specific operating systems.commands = ["xcodebuild"]requires commands to resolve withcommand -von the target.gui = truerequires a GUI-capable target.
Full Gate
bun run full-gatefull-gate runs Biome lint, Biome format check, TypeScript typecheck, and Vitest.
Useful scripts:
bun run test # Vitest once
bun run test:watch # Vitest watch mode
bun run check # alias for gateLicense
MIT
