Migration only keeps fields that map
Week one on Codex, the fear is whether last year’s hooks, MCP, and plugins are still there. Detect prints a list. Import only keeps the slice that maps.
- Pick Cla or Cur by string; anything else falls to Claude Codemigration_source.rs L59
- Scan user-level config first; repo scope skips plugin detectdetect/mod.rs L330
- Sessions filter at 30 days and 50 items; older ones dropsessions/common.rs L43
- memory needs the source to support it, plus its own feature flagdetect/mod.rs L59
- hooks keep only sync command; a prompt is skipped wholehooks_cla.rs L135
- If an MCP command contains ${, drop the whole servermcp.rs L208
- Marketplace sources keep the git family and local dirs; npm is droppedsource_cla.rs L270
- Docs are renamed; product names rewrite to Codex on word boundariesrewrite.rs L39
- Sync import returns success for Sessions immediatelyservice.rs L437
- Background writes the thread later and installs remote plugins later; a closed flag refuses memoryprocessor.rs L184
Someone said it would migrate from Claude Code automatically. You thought that meant copying the whole config directory. Next day one hook is missing, one MCP server is missing, and memory is not on the list. The missing hook used type: prompt. The missing MCP command wrote ${API_KEY}.
Swap to another teammate. He uses Cursor. Codex can see his skills and hooks, not his memory. Cursor plugin config in the repo is dropped outright.
The migration crate only reads external config in. How plugins run lives next door in core-plugins. The source type has two variants: Cla is Claude Code, Cur is Cursor. A string that is not cursor falls to Claude Code. If the caller omits the source, detect goes looking in ~/.claude.
Source:codex-rs/external-agent-migration/src/migration_source.rs lines 51–67
One detect run produces at most ten kinds of entries. Each kind’s import must have a place to go. Fields pass an allowlist first. Codex recognizes 11 hook events; Claude Code emits 27. A group whose names do not map disappears whole. A single hook’s type defaults to command; a mismatch is skipped. If an MCP command or url contains ${, drop the whole server. Marketplace sources keep github, git, and local dirs. file, url, npm, settings are dropped outright.
Source:codex-rs/external-agent-migration/src/model.rs lines 53–64 · codex-rs/hooks/src/lib.rs lines 23–35 · restored-src/src/entrypoints/sdk/coreSchemas.ts lines 355–383 · codex-rs/external-agent-migration/src/hooks_cla.rs lines 131–137 · codex-rs/external-agent-migration/src/mcp.rs lines 204–210 · codex-rs/external-agent-migration/src/source_cla.rs lines 270–274
Then the docs are renamed. CLAUDE.md becomes AGENTS.md. Product names rewrite to Codex on word boundaries. Cursor only matches case-sensitive Cursor, so ordinary English cursor is left alone.
Source:codex-rs/external-agent-migration/src/rewrite.rs lines 38–49
An importer’s usual shape is an allowlist plus three exits. Fields that do not map are dropped — do not rewrite them into a look-alike. A prompt hook is not stuffed into command. MCP with placeholders is not blindly expanded. Rewrite it in another language and this table still works.
Claude Code lets repo settings carry an enabled-plugin list, so a teammate clone gets the same plugins automatically. For Codex, a repo enable list is not install authority. Installing would write user config. An untrusted repo that wants to put executables on your machine has to have that road cut first.
Detect only scans plugins in home. A comment is hardcoded: repo-controlled settings cannot be install authority. The moment Cursor sees a repo root, plugin detect returns empty. Import seeing a nonempty working directory reports repository-scoped plugin migration is not allowed.
Source:codex-rs/external-agent-migration/src/detect/mod.rs lines 330–332 · codex-rs/external-agent-migration/src/migration_source.rs lines 116–125 · codex-rs/external-agent-migration/src/plugins.rs lines 26–36
Local plugins install on the spot. Remote marketplaces enter a pending-install queue. Sessions are two cuts too: they can sit on the detect list, and sync import() returns success for Sessions without writing anything. What actually becomes a Codex thread is a background task in app-server. The caller gets an import_id first.
Source:codex-rs/external-agent-migration/src/service.rs line 437 · codex-rs/app-server/src/external_agent_migration/session_importer.rs lines 100–111
When Codex recognizes a marketplace list, it looks up the first existing file on a fixed set of relative paths. Two of the four paths are its own; two are the competitor’s. The same lookup serves both “user added a marketplace” and “migrate a marketplace from a competitor.”
Source:codex-rs/core-plugins/src/marketplace.rs lines 20–25
Install authority for executables must sit at the user level. For a product that faces arbitrary git clones, home-only is safer. Sync first returns a receipt and slow work goes to the background — that is the usual shape of a long-job API.
The scariest product switch is overwriting config you already edited. If the target hooks.json already has content, a whole-file replace would wash away handwritten hooks.
If the target hook file is nonempty, the whole item is cancelled. An existing config.toml only fills missing keys. Same-named MCP servers keep the old one. The first migration feels like filling blanks. Click migrate a second time and most entries stay off the list because they already exist.
Source:codex-rs/external-agent-migration/src/hooks_common.rs lines 13–19
memory has another door. The feature flag is off by default; the stage is UnderDevelopment. Detect does not look at this flag; the pre-import processor does. Off returns external agent memory import is disabled. Import copies by bytes and does not redact.
Source:codex-rs/features/src/lib.rs lines 998–1003 · codex-rs/app-server/src/external_agent_migration/processor.rs lines 180–185
Import fills blanks. Files the user already wrote, migration must not touch. memory is off by default because another agent’s project memory would land on disk in the clear.
DeepSeek Harness: no migration, install again
DSH’s plugin door is a thin pnpm forwarder. It initializes a profile, runs pnpm in the profile directory, then reconciles the list from the install result. Plugins are npm packages. There is no Claude Code detect and no Cursor hook import. Switching products means you reinstall. The cost is a user tax. The gain is not keeping permanent compatibility with a settings schema that keeps growing fields.
Checkedapps/cli/src/plugin.ts lines 120–133 · 2026-08-22
Grok: its own marketplace, no competitor migration
Grok copies from the marketplace root plus a relative path into its own install registry, and writes provenance. Relative paths ban .., absolute paths, and drive letters. If there is no plugin.json but a SKILL.md at the root, it synthesizes a list. The marketplace object is its own directory plus a source record. It does not read plugins the user enabled in another home.
crates/codegen/xai-grok-plugin-marketplace/src/installer.rs lines 36–45 · 2026-08-22 · Grok · Marketplace discovery and trust
Which fields enter the list
Four Claude Code assets sit on the table: a PreToolUse type: command hook, a same-event type: prompt hook, an MCP whose command contains ${API_KEY}, and a session from 40 days ago. Using the allowlist above, list what should and should not appear on the detect list.
One more: when does the caller get an import_id, and when does this session become a thread. If memory with the flag off is sent to import, which door turns it back.