| description | Migrate AI agent configuration between Claude, Cursor, Codex, Copilot, VS Code, and OpenCode formats with AgentSync. Local-only, no vault required. |
|---|
Translate configuration from one AI agent's format to another. The migrator reads local agent config files, transforms them through format-specific translators, and writes the result to the target agent's config location. This local migration path is separate from OpenCode's opt-in vault adapter.
No vault initialisation is required — migration operates on local files only.
Not the vault-format migration.
agentsync migratetranslates config between agents (Claude → Cursor, etc.). Upgrading an older vault to the current per-machine format is a separate command,agentsync vault upgrade. The two never overlap.
- Interactive (TUI): run
agentsync, press4for the Migrate tab, pick From / To / Type, press Shift-P to preview, then Shift-A to apply. Apply is disabled until a preview that matches the current form has been rendered, so an accidental keystroke cannot trigger an unreviewed write. - Scripted (CLI): use the flags below from a shell or CI. The TUI calls the same planner internally, so the dry-run output is identical.
agentsync migrate --from <agent> --to <agent|all> [--type <type>] [--name <file>] [--dry-run]| Flag | Required | Values | Description |
|---|---|---|---|
--from |
yes | claude, cursor, codex, copilot, vscode, opencode | Source agent |
--to |
yes | claude, cursor, codex, copilot, vscode, opencode, all | Target agent(s) |
--type |
no | global-rules, mcp, commands, skills, rules, agents | Filter to one config type |
--name |
no | artefact name (requires --type) | Migrate one exact artefact. Recursive Claude agents and OpenCode commands/agents use the source-relative filename, such as teams/reviewer.md. Hard-errors if not found. |
--dry-run |
no | — | Preview without writing |
| From \ To | Claude | Cursor | Codex | Copilot | VS Code | OpenCode |
|---|---|---|---|---|---|---|
| Claude | — | GR, MCP, CMD, SK, RU, AG | GR, MCP, CMD†, SK, RU, AG | GR, MCP, CMD, SK, AG | MCP, AG | GR, MCP, CMD, SK§, AG |
| Cursor | GR, MCP, CMD, SK, RU, AG | — | GR, MCP, CMD†, SK, RU, AG | GR, MCP, CMD, SK, AG | MCP, AG | GR, MCP, CMD, SK, AG |
| Codex | GR, MCP, SK, RU, AG | GR, MCP, SK, RU, AG | — | GR, MCP, SK, AG | MCP, AG | GR, MCP, SK§, AG |
| Copilot | GR, MCP, CMD, SK, AG | GR, MCP, CMD, SK, AG | GR, MCP, CMD†, SK, AG | — | MCP‡ | GR, MCP, CMD, SK, AG |
| VS Code | MCP, AG | MCP, AG | MCP, AG | MCP‡ | — | MCP, AG |
| OpenCode | GR, MCP, CMD, SK, AG | GR, MCP, CMD, SK, AG | GR, MCP, CMD†, SK, AG | GR, MCP, CMD, SK, AG | MCP, AG | — |
GR = global-rules · MCP = MCP servers · CMD = commands · SK = skills · RU = rules folder · AG = custom agents
† Codex has no native slash-commands surface, so commands → codex wraps each command as a Codex skill at ~/.agents/skills/<name>/SKILL.md with a synthesised name/description frontmatter. Codex skills are user-invokable as /<name>, so the wrapped form actually executes — unlike previous versions which wrote into ~/.codex/rules/, where Codex never loaded the file.
‡ Copilot CLI and VS Code use one physical custom-agent store. Direct agents migration between those two logical names is rejected before writes; their separate MCP endpoints remain migratable. Without --type, supported non-agent categories are written, agents are skipped with an error, and the command exits 1. Use --type mcp for an MCP-only migration that exits successfully.
§ OpenCode normally discovers Claude's ~/.claude/skills/ and Codex's shared ~/.agents/skills/, so migration avoids a duplicate native copy. OPENCODE_DISABLE_EXTERNAL_SKILLS disables both shared roots. OPENCODE_DISABLE_CLAUDE_CODE or OPENCODE_DISABLE_CLAUDE_CODE_SKILLS disables the Claude root. When discovery is disabled, AgentSync copies the skill to OpenCode's native root. A legacy Codex skill under $CODEX_HOME/skills/ is always copied.
| Category | Claude | Cursor | Codex | Copilot CLI | VS Code | OpenCode |
|---|---|---|---|---|---|---|
| global-rules | ~/.claude/CLAUDE.md |
inline cursor.general.rules in settings.json |
$CODEX_HOME/AGENTS.md |
~/.copilot/copilot-instructions.md |
— | <config>/AGENTS.md |
| mcp | ~/.claude.json mcpServers{} |
~/.cursor/mcp.json mcpServers{} |
$CODEX_HOME/config.toml [mcp_servers.<id>] |
~/.copilot/mcp-config.json mcpServers{} |
<user>/mcp.json servers{} |
effective global sources; writes selected opencode.json[c] mcp{} |
| commands | ~/.claude/commands/*.md |
~/.cursor/commands/*.md |
~/.agents/skills/<n>/SKILL.md (wrapped) |
~/.copilot/prompts/*.prompt.md |
— | <config>/{command,commands}/**/*.md; writes commands/ |
| skills | ~/.claude/skills/<n>/SKILL.md |
~/.cursor/skills/<n>/SKILL.md |
~/.agents/skills/<n>/SKILL.md (preferred) or ~/.codex/skills/<n>/ (legacy fallback) |
~/.copilot/skills/<n>/SKILL.md (best-effort: no documented loader yet) |
— | <config>/{skill,skills}/**/SKILL.md; writes skills/ |
| rules | ~/.claude/rules/*.md |
~/.cursor/rules/*.{md,mdc} |
~/.codex/rules/*.md |
— | — | — |
| agents | ~/.claude/agents/**/*.md |
~/.cursor/agents/*.md |
$CODEX_HOME/agents/*.toml |
~/.copilot/agents/*.agent.md |
shared ~/.copilot/agents/*.agent.md |
<config>/{agent,agents}/**/*.md; writes agents/ |
- Copilot CLI rules and VS Code rules are workspace-relative (
.github/instructions/*.instructions.md). agentsync targets global artefacts only. - VS Code skills — no SKILL.md loader exists; VS Code custom agents are handled by the separate
agentstype. - VS Code commands — VS Code's
chat.promptFilesLocationsis user-configurable with no canonical default. agentsync stays out of MCP-only territory for VS Code rather than guess at a path. - Cursor
.mdcProject Rules under.cursor/rules/are workspace-only and not migrated. The~/.cursor/rules/dir IS migrated as a global counterpart. - OpenCode rules directories are not migrated. Global
AGENTS.mdis the supported rules surface; project-level rules are outside this global-only command.
OpenCode's default global config directory is $XDG_CONFIG_HOME/opencode, falling back to ~/.config/opencode. AgentSync reads global config from lowest to highest precedence:
- default
config.json - default
opencode.json - default
opencode.jsonc OPENCODE_CONFIG_DIR/opencode.jsonOPENCODE_CONFIG_DIR/opencode.jsonc
JSON configuration and command, agent, and skill directory discovery are additive: when OPENCODE_CONFIG_DIR is set, AgentSync reads both the default and custom directories. Global AGENTS.md is different. It comes only from OPENCODE_CONFIG_DIR when set, otherwise from the default directory. Writes use that same active directory for AGENTS.md, commands, skills, agents, and MCP. Each config file's MCP entries are validated before layers merge, matching OpenCode's per-file loading contract. MCP writes select only that active directory's existing opencode.jsonc, then opencode.json, or create opencode.json. AgentSync patches incoming servers into that one document. It does not copy lower-precedence effective servers into it. Target-only servers, comments outside replaced server values, and unrelated keys remain intact. A colliding server value in the selected document is replaced as a unit. If the same incoming server name exists in a lower-precedence document, migration stops and asks you to consolidate the layered definition first. This prevents OpenCode's recursive merge from attaching lower-layer credentials or transport fields to the incoming server.
Any non-empty OPENCODE_CONFIG or OPENCODE_CONFIG_CONTENT value is rejected with an actionable error. AgentSync cannot safely choose a global write target while either external-file or inline configuration is active. These variables and OPENCODE_CONFIG_DIR are interpreted literally, including whitespace, to match OpenCode's environment handling.
OpenCode Boolean flags use the runtime's case-sensitive values: true, yes, on, 1, and y; or false, no, off, 0, and n. Unset flags default to false. AgentSync rejects any other configured value before migration.
OpenCode writes preflight that each path remains under the active config root, reject existing symbolic-link or non-directory path components and symbolic-link or non-file targets, then flush an exclusive sibling temporary file before rename. Existing file modes are preserved. Command, agent, and selected skill batches preflight all target paths and normalized identities before the first write. Skill packages still apply sequentially. These batches are not filesystem transactions and directory traversal is not claimed to be race-free.
The local migrate path deliberately excludes vault operations, TUI configuration, plugins, tools, themes, modes, project/account/remote configuration, auth/runtime/data/cache files, network fetching of remote configuration or referenced content, and a dedicated rules directory. OpenCode's separate opt-in vault adapter is documented in Operations. See the official OpenCode documentation for config precedence, rules, commands, skills, agents, and MCP servers.
flowchart TD
classDef input fill:#1a5276,color:#ffffff
classDef process fill:#1e8449,color:#ffffff
classDef decision fill:#7d3c98,color:#ffffff
classDef output fill:#b9770e,color:#ffffff
classDef skip fill:#922b21,color:#ffffff
CLI["CLI: parse --from, --to, --type, --name, --dry-run"]:::input
VALIDATE["Validate agent names and flags"]:::process
RESOLVE["Resolve targets and config types"]:::process
LOOP["For each target + config type"]:::process
LOOKUP["Lookup translator in registry"]:::decision
NO_TRANSLATOR["Skip: no translator"]:::skip
READ["Read source artefacts"]:::process
TRANSLATE["Translate content"]:::process
PREFLIGHT["Agents: authority and batch-collision preflight"]:::decision
DETECT{"MCP? Check for secrets"}:::decision
ABORT["ABORT: secrets found"]:::skip
DRYRUN{"Dry run?"}:::decision
PREVIEW["Log preview"]:::output
WRITE["Write to target"]:::process
REPORT["Print summary"]:::output
CLI --> VALIDATE --> RESOLVE --> LOOP
LOOP --> LOOKUP
LOOKUP -->|"not found"| NO_TRANSLATOR
LOOKUP -->|"found"| READ --> TRANSLATE --> PREFLIGHT --> DETECT
DETECT -->|"secrets"| ABORT
DETECT -->|"clean"| DRYRUN
DRYRUN -->|"yes"| PREVIEW
DRYRUN -->|"no"| WRITE
WRITE --> REPORT
PREVIEW --> REPORT
NO_TRANSLATOR --> LOOP
- Overwrite on collision: If the target already has a matching entry, the source value wins.
- MCP per-server merge: Only colliding server names are overwritten; target-only servers are preserved. Each colliding server value is replaced as a unit so fields from an old transport cannot survive. The merge key is target-specific: VS Code uses top-level
servers+inputs, Claude/Cursor/Copilot CLI usemcpServers, Codex uses[mcp_servers.*], and OpenCode patchesmcpin only the selected highest-precedence target document. OpenCode rejects an incoming name that also exists in a lower-precedence document, or a referenced lower-layer server name that AgentSync cannot resolve, because the runtime would recursively merge those definitions. - Secret detection: A case-insensitive
Authorizationheader containing literal credential material or a literal OAuthclientSecretaborts migration without echoing the value. AgentSync never resolves or reads OpenCode{env:...}and{file:...}references. Exact environment-backed headers use target-native reference fields where verified; file references and composite values fail closed when the target cannot preserve them. The final serialized MCP bytes also pass through the existing token scanner. - Graceful skipping: Missing source files and unsupported pairs produce skip messages, not errors.
--nameis strict: When--nameis set and zero source artefacts match that exact identity, migration hard-errors. Recursive Claude agents and OpenCode commands/agents use a source-relative filename such asteams/reviewer.md.- Global-rules imports fail closed: For
--type global-rules, active Claude@pathimports fail closed when migrating to OpenCode, which does not expand them. The reverse route applies the same guard because Claude would activate a reference that was inert in OpenCode. Imports inside Markdown code spans or fenced code blocks remain literal.
agentsync migrate --type agents translates user-level custom agents across five physical formats:
- Claude recursively reads
~/.claude/agents/**/*.md;--nameis the exact source-relative filename, such asteams/reviewer.md. Claude requires YAMLnameanddescription, with the prompt in the Markdown body. Claude Code custom subagents - Cursor reads direct
~/.cursor/agents/*.mdchildren. YAMLnameis optional and otherwise derives from the filename;description,model,readonly, andis_backgroundfollow Cursor's documented format. Cursor subagents - Codex reads direct
$CODEX_HOME/agents/*.tomlchildren.name,description, anddeveloper_instructionsare required;CODEX_HOMEdefaults to~/.codex. Codex custom agents and environment variables - Copilot CLI and VS Code read direct
~/.copilot/agents/*.agent.mdchildren as one physical format.target: github-copilotandtarget: vscodeselect a logical consumer; an omittedtargetselects both. The Markdown prompt is limited to 30,000 characters. GitHub custom-agent configuration and VS Code custom agents - OpenCode recursively reads both
<config>/agent/**/*.mdand<config>/agents/**/*.md. The source-relative filename stem is the identity, soagents/teams/reviewer.mdisteams/reviewer;--nameuses the exact source-relative filename.descriptionis required andmodemust besubagent. OpenCode targets are written under the active config directory'sagents/root. Restrictivepermission,tools, ordisablevalues and thesteps/maxStepsexecution bound fail closed when another format cannot preserve their authority. A positive ClaudemaxTurnsmaps to OpenCodesteps; malformed caps are rejected. OpenCode agents
Agent discovery skips hidden, symbolic-link, and non-file entries. A missing source root is treated as no agents; unsafe or non-directory roots and directory/file read failures abort before writes. Target preflight rejects a symlinked agent root, symlinked or non-file destinations, Windows-reserved characters and device names, and trailing dots or spaces. These are AgentSync filesystem policies, separate from vendor identity rules. AgentSync does not impose a shared filename-length rule.
Agent translation is fail-closed for authority. Claude-to-shared tool translation emits a GitHub capability alias only when the exact documented Claude tools cover that whole capability group; partial groups are rejected to avoid widening access. Shared aliases are case-insensitive and expand to the full applicable Claude group; shared * and omitted Claude tools both mean all tools, while an empty list remains empty. Cursor readonly: true maps to Codex sandbox_mode = "read-only". An omitted or read-only Codex sandbox becomes Cursor readonly: true; Codex-to-Claude or shared translation rejects the inherited sandbox because those targets have no verified equivalent. Shared invocation controls (disable-model-invocation, user-invocable, infer) are type-checked; restrictive values and the VS Code agents subagent control are rejected. Explicit nonrestrictive invocation values are dropped with named warnings. Unknown fields, unknown tool names, and unmappable tool, sandbox, permission, hook, MCP, skill, memory, or isolation controls produce a per-file error and no target write. Known non-authority loss, such as an incompatible model, produces a warning naming the field.
For shared sources, the .agent.md filename stem is the programmatic identity; optional name is display metadata and warns when translation drops a different value. Each physical target is planned before writes. Duplicate logical identities and Unicode-normalized or case-equivalent target paths abort that target batch, so a collision cannot leave a partial target set. An existing shared destination is overwritten only when its target coverage exactly matches the incoming logical coverage; malformed, opposite, narrower, or broader ownership is rejected. Other --to all physical targets may still proceed. Agent writes stage and flush a same-directory exclusive temporary file before rename, but the batch is not a multi-file transaction and directory traversal is not claimed race-free. --dry-run performs the same translation and preflight without writing. --to all writes one shared Copilot/VS Code file with no target; direct logical targets write an explicit target value.
agentsync migrate --type skills --from <agent> --to <agent|all> cross-translates SKILL.md packages between Claude, Cursor, Codex, and OpenCode. OpenCode recursively reads both singular and plural native roots. OpenCode source and target skills require name and description; name must use 1–64 lowercase alphanumeric characters separated by single hyphens and match the containing directory, while description is limited to 1–1024 characters. Valid targets copy to the active config directory's skills/ root. Copilot CLI is supported as a best-effort target — files land in ~/.copilot/skills/<name>/ but Copilot does not yet implement a documented SKILL.md loader, so a warning is emitted. VS Code is skipped (no SKILL.md surface).
Each skill directory's supporting files (reference.md, scripts/*, assets) ride along as sidecars — they're written under the destination skill dir verbatim. Binary assets are base64-roundtripped. Skill names are validated against the same traversal/hidden-name guard the snapshot path uses. A nested directory with its own SKILL.md is migrated as an independent skill and is not copied into its parent package as a sidecar.
Skills targeting OpenCode fail closed when source frontmatter contains vendor authority controls that OpenCode's skill loader would ignore, including tool restrictions, invocation controls, hooks, execution context, or shell/path controls. The same fields in an OpenCode source fail closed before they can become active permissions in another vendor's loader. Claude skill shell interpolation also fails closed for OpenCode because OpenCode loads the body literally. In the reverse direction, OpenCode skill bodies containing active Claude shell or @path syntax fail closed for a Claude target because Claude would execute text that OpenCode leaves inert. Inline shell interpolation is active only at line start or after whitespace. Genuine inline code spans remain literal, but Claude still scans ordinary fenced contents and recognizes multiline shell blocks anywhere in the body, so active shell syntax there fails closed. This validation also runs when OpenCode discovers a shared Claude or Codex skill without copying it.
For Codex sources, the migrate command prefers ~/.agents/skills/ (the current spec, cites agentskills.io) and falls back to the legacy ~/.codex/skills/ only when the former is missing. OpenCode normally discovers the shared Claude and Codex roots, so AgentSync reports a warning and avoids a native duplicate. OPENCODE_DISABLE_EXTERNAL_SKILLS disables both shared roots; OPENCODE_DISABLE_CLAUDE_CODE and OPENCODE_DISABLE_CLAUDE_CODE_SKILLS independently disable Claude discovery. A disabled shared root is copied into native OpenCode storage. The legacy Codex root is not shared and is always copied.
Hidden, symbolic-link, and non-file skill entries are skipped. Unsafe or non-directory OpenCode source roots, invalid native skill metadata, unsafe targets, and target preflight failures abort the affected migration. Every selected target's SKILL.md and sidecars are containment-checked before the first package write and written through a staged same-directory rename. If a skill is symlinked from a shared repo, copy it into the source root as a real directory before migrating.
agentsync migrate --type rules --from <agent> --to <agent|all> cross-translates the global rules folder between Claude (~/.claude/rules/), Cursor (~/.cursor/rules/), and Codex (~/.codex/rules/). Copilot CLI and VS Code rules live workspace-relative under .github/instructions/ and are intentionally excluded.
Cursor's .mdc frontmatter (description, globs, alwaysApply) is stripped on egress to claude/codex with a warning naming the dropped fields, and the filename is rewritten .mdc → .md. The reverse direction (claude/codex → cursor) writes plain .md (no frontmatter synthesis — the source has no scoping information to translate).
OpenCode command discovery recursively reads both <config>/command/**/*.md and <config>/commands/**/*.md; --name is the exact source-relative filename. Hidden, symbolic-link, and non-file entries are skipped, while unsafe roots and read failures abort. When an OpenCode command targets another format, agent, subtask, and inert vendor-authority frontmatter fail closed because the target could change their meaning. OpenCode-to-Cursor, Codex, and Copilot command bodies containing active !`command` or @file syntax also fail closed because those targets have no verified equivalent. OpenCode-to-Claude accepts compatible standalone substitutions and file references, but rejects multiline shell blocks anywhere in the body and shell substitutions attached directly to other text because Claude parses them differently. Commands targeting OpenCode fail closed when source frontmatter contains authority controls, including agent and subtask, that OpenCode would activate without a verified source equivalent. Cursor and Copilot command bodies containing OpenCode's ! shell or @file interpolation syntax are rejected because those sources define plain Markdown rather than executable interpolation. Claude inline shell interpolation migrates only where Claude and OpenCode use the same whitespace boundary; Claude multiline shell blocks and inline forms that OpenCode would newly activate fail closed. AgentSync resolves every final command path before writing; existing symbolic-link components, unsafe paths, and incoming or existing NFC/case-fold identity collisions abort that target's entire command batch.
Codex has no native slash-commands surface. Earlier agentsync versions wrote ~/.codex/rules/*.md as a degraded target, but Codex never actually loaded those files as commands. This release wraps each command as a Codex skill instead:
~/.claude/commands/lint.md→~/.agents/skills/lint/SKILL.md- Frontmatter synthesis:
name: <basename>,description: <from source frontmatter, or first body paragraph, or "Migrated from <source> command"> - Compatible source frontmatter (
allowed-tools,argument-hint,model) passes through. - Body is preserved verbatim.
After migration, the command is invokable in Codex as /<basename>. A warning is emitted to make the transformation visible. Existing ~/.codex/rules/*.md files written by previous agentsync versions are not removed by this command — clean them manually if you want to avoid duplicate intent.
An unfiltered OpenCode → Codex migration preflights commands and native skills together because both write under ~/.agents/skills/. A normalized command/skill destination collision aborts both batches before either writes.
AgentSync parses each source MCP config into a transport-aware intermediate
model before writing the target. The model represents a discriminated union
over stdio, http, and sse transports, common cwd, enabled, and
timeout fields, plus VS Code top-level inputs and sandbox and per-server
sandboxEnabled, dev, and envFile metadata.
| Target | Schema | Supported transports | Lossy fields |
|---|---|---|---|
| VS Code | top-level servers + inputs (docs) |
stdio, http, sse | enabled: false, OpenCode oauth: false, Codex stdio or non-local execution contexts, and unmapped Codex tool/OAuth authority fail closed; enabled: true, timeout, and compatible non-authority losses are named |
| Claude | top-level mcpServers (stdio-only) |
stdio | disabled stdio servers fail closed; non-stdio servers are dropped with warning; inputs and stdio-only envFile/sandbox/sandboxEnabled/dev are named losses |
| Cursor | top-level mcpServers (stdio-only) |
stdio | same as Claude |
| Codex | TOML [mcp_servers.<name>] |
stdio or URL transport, inferred from command or url |
inputs, SSE distinction, unsupported non-authority OAuth fields, and unsupported metadata are named losses; VS Code sandboxing and enterprise-managed OAuth fail closed |
| OpenCode | top-level mcp (docs) |
local, remote |
inputs, compatible VS Code-only metadata, and an SSE-vs-HTTP distinction are named losses; enterprise-managed OAuth, unsupported VS Code variables, and Copilot stdio environment isolation fail closed |
OpenCode local servers preserve the command array, working directory, environment, enabled state, and timeout. Empty command arguments are preserved; the executable itself must be non-empty. VS Code MCP input accepts JSONC comments and trailing commas. VS Code numeric environment values convert to their equivalent strings; null fails closed because OpenCode has no removal-value equivalent. Remote servers preserve the URL, headers, enabled state, timeout, and OAuth configuration, including literal oauth: false. A disabled OpenCode server fails closed for VS Code; a disabled local server also fails closed for stdio-only targets because dropping the flag would activate it. Codex preserves the flag. Other unrepresentable fields and transports produce named warnings. A VS Code top-level sandbox policy is always named when a target cannot preserve it.
Codex stdio entries map command, args, env, cwd, enabled, and tool_timeout_sec. Remote entries map url, http_headers, env_http_headers, bearer_token_env_var, oauth.client_id, scopes, enabled, and tool_timeout_sec. Codex environment-backed headers become OpenCode {env:...} references. AgentSync warns because missing or empty variables have different runtime behaviour: Codex omits a missing environment header and rejects a missing bearer variable, while OpenCode substitutes an empty string. Codex-to-OpenCode stdio migration fails closed because Codex restricts the child environment while OpenCode inherits the full host environment. An omitted or explicit local Codex environment_id is representable; a non-local ID fails closed because OpenCode has no executor boundary. Codex MCP tool allowlists, denylists, approval modes, omit_tools_from, oauth_resource, and ChatGPT authentication also fail closed for OpenCode because it has no verified equivalents. An explicit empty Codex OAuth scope list fails closed because OpenCode would treat an omitted scope as eligible for discovery. OpenCode request timeout milliseconds convert to Codex seconds only when the conversion is exact. AgentSync does not invent Codex type, headers, auth, or generic timeout keys.
Codex-to-VS Code migration also fails closed for stdio servers because VS Code inherits the host environment, for non-local environment_id values, and for tool filters, approval controls, omit_tools_from, ChatGPT authentication, OAuth scopes, or oauth_resource. In the reverse direction, exact VS Code ${env:NAME} header values map to Codex env_http_headers, and Authorization: Bearer ${env:NAME} maps to bearer_token_env_var; input variables, composite values, and variable header names fail closed. A VS Code server with sandboxEnabled: true fails closed for Codex because Codex has no equivalent filesystem and network sandbox.
Copilot CLI local servers fail closed for OpenCode because Copilot inherits only PATH plus configured variables while OpenCode passes the full host environment. Canonical Copilot type: "local", timeout, type: "streamable-http", and unrestricted tools: ["*"] are normalized before this check. Missing, malformed, empty, or restricted tool lists fail closed because OpenCode has no per-server tool filter. Static public authorization-code OAuth maps oauthClientId to OpenCode oauth.clientId; the equivalent Copilot defaults oauthPublicClient: true, oauthGrantType: "authorization_code", and oidc: false are omitted. Confidential clients, client-credentials grants, and enabled OIDC fail closed. For remote servers, $VAR and ${VAR} header references become OpenCode {env:VAR} references. Copilot ${VAR:-default} headers fail closed because OpenCode has no equivalent default syntax.
In the reverse direction, OpenCode local servers fail closed for Copilot because the full inherited environment cannot be reduced to Copilot's PATH-only default without changing behaviour. OpenCode remote servers become canonical Copilot type: "http" entries with tools: ["*"]; AgentSync warns that OpenCode's HTTP-to-SSE fallback is not preserved. enabled: false fails closed because Copilot has no verified disabled equivalent, while explicit enabled: true is dropped with a named warning. Header {env:VAR} references become Copilot ${VAR} references, while file references and Copilot variable syntax that was literal in OpenCode fail closed. Public OAuth clientId maps to oauthClientId; oauth: false, client secrets, scopes, callback ports, and redirect URIs fail closed because Copilot's file schema has no equivalent.
For OpenCode-to-Codex migration, an exact {env:VAR} header becomes env_http_headers, and Authorization: Bearer {env:VAR} becomes bearer_token_env_var. OpenCode file references, composite reference values, and references in fields without a verified Codex equivalent fail closed without being resolved. OpenCode OAuth clientId and whitespace-delimited scope map to Codex oauth.client_id and scopes; clientSecret, callbackPort, and redirectUri are named losses after the secret gate runs.
VS Code OAuth is written under oauth, never auth. AgentSync maps clientId; enterpriseManaged: true fails closed for Codex and OpenCode because neither target preserves the enterprise identity-provider boundary, while explicit false is a named loss. OpenCode-compatible OAuth fields are preserved when VS Code is the source and the OpenCode target can represent them. OpenCode oauth: false fails closed for VS Code because omitting the field would enable automatic OAuth. OpenCode {env:...} values become VS Code ${env:...} variables. In the reverse direction, VS Code ${env:NAME} values become OpenCode {env:NAME} references. Other VS Code variables, variable-bearing object keys, literal OpenCode references in VS Code values, OpenCode file references, and referenced object keys fail closed because the target has no verified equivalent.
Malformed per-server entries abort the MCP translation before any valid sibling can be written. When a target cannot represent a transport, AgentSync emits an explicit
translator warning that names the dropped server and the unsupported
transport (e.g. vscode → claude (mcp): Dropped server "remote": transport "http" is not representable…). Warnings flow into MigrateResult.warnings
so the CLI surfaces them — they are never dropped silently.
If a source migrates to a stdio-only target where every server is
unrepresentable (e.g. an all-HTTP VS Code config → Claude), the translator
skips the write rather than creating a brand-new file containing only
{"mcpServers":{}}. The warning still surfaces in MigrateResult.warnings
so the operator hears about the dropped servers.
agentsync migrate --from claude --to cursor --dry-run # preview
agentsync migrate --from claude --to cursor # applyagentsync migrate --from claude --to codex --type mcpagentsync migrate --from claude --to allagentsync migrate --from claude --to cursor --type commands --name review.mdagentsync migrate --from claude --to codex --type skills --name code-revieweragentsync migrate --from cursor --to claude --type rulesagentsync migrate --from claude --to copilot --type mcpagentsync migrate --from opencode --to claude --dry-runagentsync migrate --from claude --to opencodeagentsync migrate --from claude --to codex --type commands
# writes ~/.agents/skills/<name>/SKILL.md per command