This document records the Claude cleanup as the reference contract for future platform sync work. The goal is one-click third-party API sync without turning platform config into a broad preference or routing policy layer.
env/platforms/<platform>.json is a sync source, not a complete mirror of the
target tool's local config.
The syncer may only touch fields it explicitly owns:
- MCP server blocks declared by
env/mcp/*.json. - API fields declared by the platform config and gated by
api.enabled. - Preamble / skills metadata declared under
preamble. - Platform-specific generated blocks with stable managed markers or sidecars.
All unrelated user fields in the target config must be preserved.
Default sync should stay narrow:
- API sync.
- MCP servers.
- Skills / preamble / agents metadata.
Default sync should not include:
- Platform UI preferences.
- Personal editor / shell / notification settings.
- Model preference policy.
- Automatic model routing.
- Complexity scoring, two-stage routing, cost optimization, or fallback policy.
If a platform later needs one of those policies, it must be added as an explicit opt-in feature, not as a default side effect of API sync.
Each platform that supports third-party API sync may use:
{
"api": {
"enabled": true
}
}Rules:
api.enabled=truemeans sync this platform's API fields.api.enabled=falsemeans do not sync API fields and clean fields owned by the syncer.- The toggle is local to this repository checkout and this machine.
- Do not add a parallel
SYNC_<PLATFORM>_APIenvironment switch. - Do not introduce
<platform>.local.jsonfor this toggle. - Missing default is platform-specific and must be documented.
For Claude, missing api or missing api.enabled defaults to enabled.
Claude is the current reference implementation.
env/platforms/claude.json should stay close to:
{
"api": {
"enabled": true
},
"env": {
"ANTHROPIC_AUTH_TOKEN": "${claude.token}",
"ANTHROPIC_BASE_URL": "${claude.url}",
"CLAUDE_CODE_EFFORT_LEVEL": "medium",
"CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS": "1"
},
"preamble": {
"target": "CLAUDE.md",
"mode": "full",
"tool": "claude-code",
"agents": true
}
}Claude sync owns these target fields:
| Target | Owned fields |
|---|---|
~/.claude.json |
mcpServers |
~/.claude/settings.json |
API env keys declared in env/platforms/claude.json |
~/.claude/config.json |
primaryApiKey only when its value is self or API sync is enabled |
~/.claude/CLAUDE.md |
Managed preamble blocks only |
~/.claude/agents/ |
Legacy router agent cleanup only; no default model-routing generation |
Claude API behavior:
api.enabled=true: merge API env into~/.claude/settings.jsonand set~/.claude/config.jsonprimaryApiKeytoself.api.enabled=false: remove sync-managed API env keys and removeprimaryApiKeyonly if its current value isself.- Existing unrelated settings, env keys, and config keys must survive.
~/.claude/config.jsonis created when Claude root exists and API sync is enabled.
Claude default sync must not write these model routing fields:
{
"ANTHROPIC_DEFAULT_OPUS_MODEL": "...",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "...",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "..."
}preamble.agents=true means the platform participates in preamble / agents
capability sync. It does not mean HAIKU / SONNET / OPUS routing, generated
router agents, or automatic model selection.
CodeBuddy is the second platform with an explicit api.enabled toggle.
env/platforms/codebuddy.json should stay close to:
{
"api": {
"enabled": true
},
"models": [
{
"id": "deepseek-v4-pro",
"name": "DeepSeek V4 Pro",
"vendor": "dataeyes",
"url": "${codebuddy.url}",
"apiKey": "${codebuddy.key}",
"maxInputTokens": 128000,
"maxOutputTokens": 8192,
"supportsToolCall": true,
"supportsImages": false,
"relatedModels": {
"lite": "deepseek-v4-flash",
"reasoning": "deepseek-v4-pro"
}
},
{
"id": "deepseek-v4-flash",
"name": "DeepSeek V4 Flash",
"vendor": "dataeyes",
"url": "${codebuddy.url}",
"apiKey": "${codebuddy.key}",
"maxInputTokens": 128000,
"maxOutputTokens": 8192,
"supportsToolCall": true,
"supportsImages": false
}
],
"availableModels": [
"deepseek-v4-pro",
"deepseek-v4-flash"
],
"preamble": {
"target": "CODEBUDDY.md",
"mode": "full",
"tool": "codebuddy"
}
}Answers to the platform-addition questions:
- Target files:
~/.codebuddy/models.json(models+availableModels),~/.codebuddy/mcp.json(MCP),~/.codebuddy/CODEBUDDY.md(full preamble, rendered bysync-agent-preamble.shand embedding the historical-recall trigger),~/.codebuddy/skills/(skills copied from Claude). - API sync fields:
modelsandavailableModelsinside~/.codebuddy/models.json. - Default for
api.enabled:true. CodeBuddy historically always synced its models, so a missingapiblock or missingapi.enabledkeeps the old always-sync behavior. Only an explicitfalsedisables it. - Owned target fields:
~/.codebuddy/models.json→models,availableModels(both gated byapi.enabled); MCP servers; the preamble block — the full preamble (incl. the embedded historical-recall trigger) whenpreamble.mode=full, or the standalone historical-recall managed block whenpreamble.mode=recall, both rendered bysync-agent-preamble.sh; synced skill directories. - Cleanup when
api.enabled=false: setavailableModelsto an empty list[]rather than removing the key (CodeBuddy special handling — provider model definitions stay so they can be re-enabled, but nothing is shown in the model picker). Config-managedmodelsare NOT merged while disabled; existing model definitions are neither synced nor deleted. - Unrelated user fields preserved: any top-level key other than
models/availableModelsinmodels.json(e.g.meta,uiPreference), user-added model entries, user-added MCP servers, and user content outside the managed block inCODEBUDDY.md. - MCP servers are independent of API sync — they still sync when
api.enabled=false. - Skills / preamble are independent of API sync — they still sync when
api.enabled=false. - No login-bypass field like Claude
primaryApiKey=self. - Tests live in
tests/test_codebuddy_sync.pyand cover enable-by-default, disable-empty, user-model preservation, idempotent re-sync, and re-enable-restore.
Gemini is the third platform with an explicit api.enabled toggle.
env/platforms/gemini.json should stay close to:
{
"api": {
"enabled": true
},
"model": {
"name": "gemini-3.5-flash",
"maxSessionTurns": -1,
"compressionThreshold": 0.5,
"skipNextSpeakerCheck": true
},
"context": { "fileName": "GEMINI.md", "includeDirectoryTree": true },
"tools": { "sandbox": "sandbox-exec", "sandboxNetworkAccess": true },
"skills": { "enabled": true },
"hooksConfig": { "enabled": true },
"security": { "folderTrust": { "enabled": true } },
"experimental": {
"directWebFetch": true,
"enableAgents": true,
"autoMemory": true,
"contextManagement": true
},
"contextManagement": {
"historyWindow": { "maxTokens": 200000, "retainedTokens": 10000 }
},
"export_env_to_zshrc": {
"GEMINI_API_KEY": "${gemini.key}",
"GOOGLE_GEMINI_BASE_URL": "${gemini.url}",
"GEMINI_MODEL": "gemini-3.5-flash"
},
"preamble": { "target": "GEMINI.md", "mode": "full", "tool": "gemini" }
}Answers to the platform-addition questions:
- Target files:
~/.gemini/settings.json(model+ general settings +mcpServers),~/.zshrc(managed GEMINI env block),~/.gemini/GEMINI.md(recall/preamble), and the Xcode CodingAssistant mirror~/Library/Developer/Xcode/CodingAssistant/gemini/settings.json. - API sync fields:
modelinside~/.gemini/settings.json, and the env vars inexport_env_to_zshrc(GEMINI_API_KEY,GOOGLE_GEMINI_BASE_URL,GEMINI_MODEL) written to~/.zshrc. - Default for
api.enabled:true. Gemini historically always synced its model and env vars, so a missingapiblock or missingapi.enabledkeeps the old always-sync behavior. Only an explicitfalsedisables it. - Owned target fields:
~/.gemini/settings.json→model(gated byapi.enabled),mcpServers(always synced);~/.zshrc→ the GEMINI env block (gated); the managed recalL/preamble block inGEMINI.md. - Cleanup when
api.enabled=false:modelis excluded from the managed settings and pruned from~/.gemini/settings.jsonvia the managed-keys sidecar; the managed~/.zshrcGEMINI env block is removed byclear_env_block. - Unrelated user fields preserved: any top-level key in
settings.jsonother thanmodel(e.g. userui,general, nested custom sub-keys), user-added MCP servers, other platforms'~/.zshrcblocks, and user content inGEMINI.md. - MCP servers are independent of API sync — they still sync when
api.enabled=false. - General settings (
context,tools,skills,hooksConfig,security,experimental,contextManagement) and the preamble are independent of API sync — they still sync whenapi.enabled=false. - No login-bypass field like Claude
primaryApiKey=self. - Tests live in
tests/test_gemini_sync.pyand cover enable-by-default, disable-removes-model, disable-cleans-zshrc, idempotent re-sync, and re-enable-restore.
Qwen Code is a platform that mirrors ~/.qwen/settings.json. env/platforms/qwen.json
flattens the synced fields to the top level (no settings wrapper) so its
structure matches ~/.qwen/settings.json exactly; model definitions live in
~/.qwen/models.json, which Qwen owns and this syncer does not manage.
env/platforms/qwen.json should stay close to:
{
"api": {
"enabled": true
},
"security": {
"auth": {
"selectedType": "openai"
}
},
"env": {
"__AUTO__": "${qwen.key}"
},
"modelProviders": {
"openai": [
{
"id": "qwen3-coder-plus",
"name": "Qwen3 Coder Plus",
"baseUrl": "${qwen.url}",
"envKey": "__AUTO__",
"generationConfig": {
"extra_body": {
"enable_thinking": true
}
}
},
{
"id": "qwen3-coder",
"name": "Qwen3 Coder",
"baseUrl": "${qwen.url}",
"envKey": "__AUTO__",
"generationConfig": {
"extra_body": {
"enable_thinking": true
}
}
},
{
"id": "qwen-max",
"name": "Qwen Max",
"baseUrl": "${qwen.url}",
"envKey": "__AUTO__",
"generationConfig": {
"extra_body": {
"enable_thinking": true
}
}
}
]
},
"model": {
"name": "qwen3-coder-plus",
"baseUrl": "${qwen.url}"
},
"preamble": {
"target": "QWEN.md",
"mode": "recall",
"tool": "qwen"
}
}Answers to the platform-addition questions:
- Target files:
~/.qwen/settings.json(envkeys + the top-level managed fieldssecurity,modelProviders,model),~/.qwen/skills/(skills copied from Claude),~/.qwen/QWEN.md(recall preamble — declared underpreamble, rendered by the same managed-block mechanism as the other recall platforms).~/.qwen/models.jsonis not a sync target — Qwen owns it directly. - API sync fields:
envand the owned top-level fieldssecurity/modelProviders/modelinside~/.qwen/settings.json. For custom OpenAI-compatible providers,modelProviders.*[].envKeymust use the sentinel"__AUTO__"rather than a literalDASHSCOPE_API_KEY— Qwen Code reservesDASHSCOPE_API_KEYfor its internal DashScope routing and 401s on custom endpoints. The syncer derives the real env var name (QWEN_CUSTOM_API_KEY_<PROTO>_<normalize(baseUrl)>_<sha256(proto\0origin)[:12]>, whereoriginisscheme://host) from each provider'sbaseUrl, rewrites the sentinel in bothmodelProviders.*[].envKeyand theenvblock, and remaps the declared token onto the derived name. The legacyDASHSCOPE_API_KEYis dropped fromsettings.envon every sync unless the config still declares it explicitly. - Default for
api.enabled:true. Qwen historically always synced its API fields, so a missingapiblock or missingapi.enabledkeeps the old always-sync behavior. Only an explicitfalsedisables it. - Owned target fields:
~/.qwen/settings.json→env(gated byapi.enabled),security,modelProviders,model(the last three gated byapi.enabled); synced skill directories. - Cleanup when
api.enabled=false: remove only the syncer-managedenvkeys from~/.qwen/settings.json; remove the managed top-level fields (security,modelProvidersentries byid, andmodel) ownership-aware. Model definitions are not touched (this syncer never writesmodels.json). - Unrelated user fields preserved:
~/.qwen/settings.json→$versionand any other top-level key (e.g. usermodelProvidersentries not in config, userenvkeys, usersecuritykeys outside the managed block); user content outside the managed block inQWEN.md.~/.qwen/models.jsonis left fully intact since it is not a sync target. - MCP servers:
sync/platforms/qwen.pycurrently ignoresmcp_servers(Qwen Code's MCP wiring is not yet driven byenv/mcp/*.json). - Skills / preamble are independent of API sync — they still sync when
api.enabled=false. - No login-bypass field like Claude
primaryApiKey=self. $versionis a Qwen-internal marker ("$version": 4in the real~/.qwen/settings.json) and is never written or overwritten by the syncer — every write reads the existing file and merges only owned keys, so$version(and any other user key) survives untouched.- Tests live in
tests/test_qwen_sync.pyand cover enable-by-default, settings-fields merge/cleanup,$versionpreservation, models.json not managed, idempotent re-sync, and re-enable-restore.
Continue is a platform with an explicit api.enabled toggle.
env/platforms/continue.json should stay close to:
{
"_comment": "Continue platform configuration. The 'models' block in config.yaml is synced as a third-party API definition by default; set api.enabled=false to disable API sync and remove the managed 'models' block. MCP servers and the historical-recall preamble are independent of API sync and always sync.",
"api": {
"enabled": true
},
"path": "~/.continue/config.yaml",
"models": [
{
"name": "deepseek-v4-pro",
"provider": "openai",
"model": "deepseek-v4-pro",
"apiKey": "${continue.key}",
"apiBase": "${continue.url}",
"defaultCompletionOptions": {
"maxTokens": 128000
}
}
],
"preamble": {
"mode": "recall",
"tool": "continue",
"format": "yaml"
}
}Answers to the platform-addition questions:
- Target files:
~/.continue/config.yaml(models+mcpServers+ therulesmanaged block for global historical recall). - API sync fields:
modelsinside~/.continue/config.yaml. - Default for
api.enabled:true. Continue historically always synced its model definition, so a missingapiblock or missingapi.enabledkeeps the old always-sync behavior. Only an explicitfalsedisables it. - Owned target fields:
~/.continue/config.yaml→models(gated byapi.enabled);mcpServers(always synced); the managedrulesrecall block (preamble, always synced). - Cleanup when
api.enabled=false: the entire syncer-ownedmodelsroot key is removed fromconfig.yaml(Continue replaces the block wholesale on each sync, so removal is deterministic and re-enable restores it). - Unrelated user fields preserved: any top-level key other than
modelsinconfig.yaml(e.g.name,version,contextProviders,slashCommands, usermcpServers), and userrulesentries outside the managed recall block. - MCP servers are independent of API sync — they still sync when
api.enabled=false. - Skills / preamble are independent of API sync — they still sync when
api.enabled=false. Continue has no standalone preamble markdown file; the recall block is injected intoconfig.yamlrules(preamble.format=yaml, target=None by design), so a missingpreamble.targetis intentional. - No login-bypass field like Claude
primaryApiKey=self. - Tests live in
tests/test_continue_sync.pyand cover enable-by-default, disable-removes-models, user-field preservation, idempotent re-sync, and re-enable-restore.
Cline is the fourth platform with an explicit api.enabled toggle.
env/platforms/cline.json should stay close to:
{
"api": {
"enabled": true
},
"globalState": {
"openAiBaseUrl": "${cline.url}",
"planModeOpenAiModelId": "deepseek-ai/deepseek-v4-pro",
"actModeOpenAiModelId": "deepseek-ai/deepseek-v4-flash"
},
"secrets": {
"openAiApiKey": "${cline.key}"
},
"preamble": {
"target": "rules/ai-coding-kit-recall.md",
"mode": "recall",
"tool": "cline"
}
}Answers to the platform-addition questions:
- Target files:
~/.cline/data/globalState.json(globalStatekeys),~/.cline/data/secrets.json(secretskeys), the MCP candidate paths under~/Library/Application Support/<editor>/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json(MCP),~/.cline/skills/(skills copied from Claude), and the recall preamblerules/ai-coding-kit-recall.md(rendered by the Bashsync-agent-preamble.sh, not by the Python sync — the Pythoncline.pydoes not touch the preamble file). - API sync fields:
globalStateandsecretsinside~/.cline/data/. - Default for
api.enabled:true. Cline historically always merged its globalState + secrets, so a missingapiblock or missingapi.enabledkeeps the old always-sync behavior. Only an explicitfalsedisables it. - Owned target fields:
~/.cline/data/globalState.json→ the keys declared underglobalState(gated byapi.enabled);~/.cline/data/secrets.json→ the keys declared undersecrets(gated byapi.enabled). The set of owned keys is tracked in a managed-keys sidecar (~/.cline/data/.managed_keys.json) so a key dropped from the config, or all keys on disable, are pruned on the next sync. - Cleanup when
api.enabled=false: every key the syncer currently owns (per the sidecar) is removed fromglobalState.jsonandsecrets.json, and the sidecar record is cleared so re-enabling re-merges cleanly. Existing unrelated keys and user-added keys are left intact. - Unrelated user fields preserved: any other key in
globalState.json(e.g. telemetry, welcome state), any other key insecrets.json(e.g.anthropicApiKeyfor other providers), user-added MCP servers, and user content outside the managed block in the preamble file. - MCP servers are independent of API sync — they still sync when
api.enabled=false. - Skills / preamble are independent of API sync — they still sync when
api.enabled=false(preamble is rendered by the Bash writer, not the Python sync). - No login-bypass field like Claude
primaryApiKey=self; Cline's secret key is a user-providedopenAiApiKey, never synthesized by the syncer. - Tests live in
tests/test_cline_sync.pyand cover enable-by-default, disable-cleans, idempotent re-sync, user-field preservation, re-enable-restore, and unresolved-placeholder-skip.
Codex is a platform with an explicit api.enabled toggle.
env/platforms/codex.json is intentionally lean — it carries only the
team-shared core + security/sandbox fields. Per-developer preference knobs
(reasoning effort, verbosity, personality, features, history, tui,
analytics, etc.) are deliberately NOT synced and are not present in the file.
It should stay close to:
{
"api": {
"enabled": true
},
"model": "gpt-5.5",
"sandbox_mode": "workspace-write",
"approval_policy": "on-request",
"allow_login_shell": true,
"default_permissions": ":workspace",
"sandbox_workspace_write": {
"network_access": true,
"writable_roots": [],
"exclude_tmpdir_env_var": false,
"exclude_slash_tmp": false
},
"model_provider": "dataeyes",
"model_providers": {
"dataeyes": {
"base_url": "${codex.url}",
"env_key": "DATAEYES_API_KEY",
"wire_api": "responses"
}
},
"export_env_to_zshrc": {
"DATAEYES_API_KEY": "${codex.key}"
},
"preamble": { "target": "AGENTS.md", "mode": "full", "tool": "codex" }
}Answers to the platform-addition questions:
- Target files:
~/.codex/config.toml(the# BEGIN CODEX SHAREDmanaged block plus the# BEGIN MCP SYNCblock — both live insideconfig.toml; Codex does not use a separate generated MCP file), the Xcode mirror~/Library/Developer/Xcode/CodingAssistant/codex/config.toml, and~/.zshrcfor the managedDATAEYES_API_KEYenv block (export_env_to_zshrc). - API sync fields:
model_providerandpreferred_auth_method(emitted as root keys), the[model_providers.*]tables, and theDATAEYES_API_KEYenv export. - Default for
api.enabled:true. Codex historically always synced its third-party API config, so a missingapiblock or missingapi.enabledkeeps the old always-sync behavior. Only an explicitfalsedisables it. - Owned target fields:
~/.codex/config.toml→ inside the CODEX SHARED managed block: the team-shared core + security/sandbox fields (model,sandbox_mode,approval_policy,allow_login_shell,default_permissions,sandbox_workspace_write), plusmodel_provider,preferred_auth_method, and themodel_providerstable (all gated byapi.enabled); MCP servers (always synced); the managedDATAEYES_API_KEYblock in~/.zshrc(gated). Preference knobs (reasoning effort, verbosity, personality,features,history,tui,analytics, etc.) are NOT owned and are never written. - Cleanup when
api.enabled=false: the renderer omitsmodel_provider,preferred_auth_method, and[model_providers.*]from the generated CODEX SHARED block; because the whole block is replaced on every sync, they are deleted (not commented) deterministically fromconfig.toml— matching the cleanup policy (prefer deletion over comments). The managedDATAEYES_API_KEYblock in~/.zshrcis removed byclear_env_block. An empty/unsetmodel_providerwhile API sync is enabled is still emitted as a commented placeholder (nevermodel_provider = "None"), so users can uncomment it; that placeholder is unrelated to the disable-delete path. - Unrelated user fields preserved: any
[table]or key outside the CODEX SHARED and MCP markers; preference/host-specific settings (personality,model_reasoning_effort,features,history,tui,agents,memories,analytics,feedback, editor/shell/notification prefs) are excluded from the managed block by design (defensive_HOST_SKIPincodex.py) and never written or touched, even if re-added toenv/platforms/codex.json. - MCP servers are independent of API sync — they still sync when
api.enabled=false. - Skills / preamble are independent of API sync — they still sync when
api.enabled=false. (Codex'spreambleis declared for the shared preamble mechanism; the renderer currently focuses on config.toml + MCP.) - No login-bypass field like Claude
primaryApiKey=self. - Tests live in
tests/test_codex_sync.pyand cover enable-by-default, disable-omits-api-fields, disable-clears-env-block, comment-when-provider- unset, re-enable-restore, and idempotent re-sync.
When removing a previously managed feature, prefer deletion over comments.
Reasons:
- Target configs should remain valid JSON / YAML / TOML.
- Commenting out generated fields still leaves ambiguous ownership.
- Deletion plus sidecar / managed markers gives deterministic re-sync behavior.
Cleanup must be ownership-aware:
- Delete fields recorded by a sidecar.
- Delete fields inside a managed block marker.
- Delete a special field only when the current value proves sync ownership.
- Preserve unrelated user fields.
For Claude, primaryApiKey is removed only when the value is self; another
value, such as login, is treated as user-owned and preserved.
Schema validation should reject stale or ambiguous metadata.
Current guardrails:
apimust be an object.api.enabledmust be boolean.- Unknown
api.*fields are rejected. preamblemust be an object.preamble.modemust be one offull,recall,none.preamble.formatmust be one ofmarkdown,yaml,cursor-mdc.preamble.agentsmust be boolean.preamble.routeris rejected.
Before modifying another platform, answer these questions in the implementation or review notes:
- What exact target files does this platform load at runtime?
- Which fields are API sync fields?
- What is the default for
api.enabled, and why? - Which target fields are owned by the syncer?
- How are stale fields cleaned when
api.enabled=false? - How are unrelated user fields preserved?
- Are MCP servers independent of API sync?
- Are skills / preamble independent of API sync?
- Does the platform have any special login bypass field like Claude
primaryApiKey=self? - Which tests prove enable, disable, idempotent re-sync, and user-field preservation?
Do one platform at a time. Do not copy Claude behavior blindly; copy the ownership model and verification discipline.