[config] Dynamic profile switching + hot-reload for Codex CLI/IDE (shared home config)
What version of the VS Code extension are you using?
1.104.1
Which IDE are you using?
VS Code
What platform is your computer?
Darwin 25.0.0 arm64 arm
What steps can reproduce the bug?
Current Behavior
- Cannot switch profiles at runtime.
- Manual edits to
config.jsonorconfig.tomldo not reliably reload/apply.
What is the expected behavior?
Expected Behavior
- Switch profiles dynamically from CLI/IDE and keep both in sync.
- File edits (JSON/TOML) trigger validated reload; invalid edits roll back with clear diagnostics.
What do you see instead?
Problem Statement
• No dynamic profile switching: Users can’t change active profile in a running session (IDE/CLI).
• No reliable hot-reload: Editing the shared config doesn’t consistently notify the IDE (or running CLI daemons) to apply changes.
• Risk: stale credentials/endpoints/models lead to failing requests or using the wrong environment.
Additional information
Proposal
- Config Manager with cross-platform watcher (debounced), format detection, normalization, JSON Schema validation, atomic apply, rollback.
- Broadcast reload to IDE & CLI via local IPC.
- CLI:
codex profile list|get|switch,codex config reload|validate. - IDE: Command palette actions + status bar profile indicator.
Acceptance Criteria
- Hot-reload within debounce window on macOS/Linux/Windows.
- CLI/IDE remain consistent after profile switch.
- Invalid edits roll back automatically with surfaced diagnostics.
- Workspace override (opt-in) merges deterministically and is indicated in UI.
Diagram
---
config:
theme: neo
layout: elk
look: neo
---
flowchart TD
A[Idle: Config Loaded] -->|FS change JSON/TOML| B[Debounce & Batch]
B --> C[Detect Format -> Parse]
C --> D[Normalize to JSON]
D --> E[Validate JSON Schema]
E -->|valid| F[Stage New Config]
F --> G[Atomic Swap]
G --> H[Broadcast ConfigReload]
H --> I{Consumers}
I -->|IDE| J[Apply in-memory settings + UI toast]
I -->|CLI| K[Refresh session + print note]
E -->|invalid| L[Rollback to Last Known Good]
L --> M[Notify: Validation Error + Open Config]
subgraph Profile Switching
X[User: profile switch <name>] --> Y[Update activeProfile in file preserve JSON/TOML]
Y --> H
end
---
Goals
• ✅ Switch profiles at runtime via IDE UI and CLI commands; both stay in sync.
• ✅ Hot-reload config edits (JSON/TOML) with schema validation, debounced file watching, atomic apply, and rollback on failure.
• ✅ Single source of truth with optional workspace overrides (opt-in).
• ✅ Excellent DX: clear commands, non-blocking notifications, structured logs.
• ✅ Cross-platform: macOS (FSEvents/kqueue), Linux (inotify), Windows (ReadDirectoryChangesW).
---
Non-Goals
• ❌ Redesigning the config data model beyond enabling JSON/TOML and profiles.
• ❌ Changing secret storage behavior.
• ❌ Telemetry beyond reload events and outcomes.
---
Proposed Solution
1) Config format handling (JSON + TOML)
• Detect format by extension (.json / .toml), parse accordingly.
• Normalize both into a canonical in-memory JSON object for validation and broadcast.
• Keep file on disk as is (no forced migration).
• Optional workspace override: <workspace>/.codex/config.(json|toml) that merges over user config (opt-in/trusted).
JSON example
{
"version": 1,
"activeProfile": "work",
"profiles": {
"work": {
"org": "acme",
"endpoint": "https://api.openai.com",
"model": "gpt-4.1-mini",
"timeoutMs": 20000
},
"personal": {
"org": "me",
"endpoint": "https://api.openai.com",
"model": "gpt-4o",
"timeoutMs": 15000
}
},
"watch": { "enabled": true, "debounceMs": 250, "strictValidation": true }
}
TOML example
version = 1
activeProfile = "work"
[profiles.work]
org = "acme"
endpoint = "https://api.openai.com/v1"
model = "gpt-5-codex"
timeoutMs = 20000
---
Developer Experience (DX)
CLI
codex profile list
codex profile switch work --persist
codex profile get
codex config validate --strict
codex config reload
IDE
• Command Palette actions: Switch Profile, Reload Config, Open Config.
• Status bar profile indicator (clickable).
• Diagnostics pane with last N reloads (format, source, outcome).
Merge rules (when workspace override is enabled)
- Load user config (JSON/TOML) → normalize.
- Overlay workspace config (JSON/TOML) → normalize & merge (keys overwrite by default; arrays replace unless future mergeStrategy is introduced).
- Apply env overrides (session-only, non-persisting).
---
Alternatives Considered
• Manual reload only → simpler but misses background edits and automation.
• Polling over FS watchers → portable but wasteful; slower feedback.
• IDE-only switching → leaves CLI behind; we want single source of truth + broadcast.
---
Test Plan
• Unit: format detection, TOML/JSON parsing, normalization, schema validation, debounce, rollback.
• Integration: FS watcher events across OSes; IPC broadcast; CLI/IDE apply path; workspace override merge.
• E2E:
• Edit config.toml → IDE/CLI update within debounce.
• Edit config.json with invalid key → rollback + surfaced diagnostics.
• codex profile switch staging from CLI → IDE profile changes instantly.
• Rapid saves (save spike) → exactly one apply.
• Regression: symlinked paths; very large profiles; read-only config; custom config path via CODEX_CONFIG.
---
Open Questions
• Should workspace overrides be opt-in with a trust prompt per workspace?
• Do we need a policy-managed read-only mode with a different visual indicator?
• Keep a local reload history file for auditing (opt-in)?
[profiles.personal]
org = "me"
name = "OpenAI"
base_url = <"https://api.openai.com/v1">
env_key = "OPENAI_API_KEY"
[watch]
enabled = true
debounceMs = 250
strictValidation = true
1) Validation & Safety
• JSON Schema validates the normalized object (works for both JSON and TOML after normalization).
• Unknown keys → warning (non-fatal) when strictValidation=false; fatal otherwise.
• Atomic apply: parse → validate → stage → swap → broadcast.
• Rollback to last-known-good on any failure; surface actionable diagnostics.
Schema (excerpt)
{
"type": "object",
"required": ["version", "activeProfile", "profiles"],
"properties": {
"version": { "type": "integer", "minimum": 1 },
"activeProfile": { "type": "string", "minLength": 1 },
"profiles": {
"type": "object",
"minProperties": 1,
"additionalProperties": {
"type": "object",
"required": ["endpoint", "model"],
"properties": {
"org": { "type": "string" },
"endpoint": { "type": "string", "format": "uri" },
"model": { "type": "string" },
"proxy": { "type": ["string", "null"] },
"timeoutMs": { "type": "integer", "minimum": 1000 }
}
}
},
"watch": {
"type": "object",
"properties": {
"enabled": { "type": "boolean" },
"debounceMs": { "type": "integer", "minimum": 50 },
"strictValidation": { "type": "boolean" }
}
}
}
}
3) Hot-reload engine
• Config Manager runs in the IDE extension host (or as a tiny helper daemon):
• Cross-platform file watcher (debounced).
• Format detection → parse (JSON/TOML) → normalize to JSON → validate.
• On success: atomic apply and broadcast a ConfigReload event.
• On failure: rollback + user notification with a “View details / Open config” action.
• Broadcast mechanisms (pick 1st available):
• IDE internal event bus to extension(s).
• Local IPC: Unix domain socket / Windows named pipe (token-guarded).
• Loopback TCP on 127.0.0.1:0 with short-lived auth token (fallback).
4) Runtime profile switching (DX)
• CLI
• codex profile list
• codex profile get
• codex profile switch <name> [--persist] → updates activeProfile in the backing JSON/TOML file and triggers the same reload pipeline.
• codex config reload (manual nudge)
• codex config validate [--strict]
• IDE
• Command Palette: “Codex: Switch Profile”, “Codex: Reload Config”, “Codex: Open Config”
• Status-bar indicator showing current profile (click to switch).
• Non-blocking toast: “Config reloaded ✓” or “Validation error ✕ (details)”.
• Env overrides (session-scoped):
• CODEX_PROFILE (non-persisting, highest precedence)
• CODEX_CONFIG (custom path)
• CODEX_NO_WATCH=1 (disable watchers)
1) Security & Privacy
• No plaintext secret logging; redact sensitive fields.
• Local-only IPC; random auth token; least-privilege file access.
• Optional read-only mode if config is policy-managed (see Open Questions).
1) Observability & Logs
• Structured events: config.changed, config.validate.ok/err, config.apply.ok/rollback, profile.switch.ok/err.
• IDE diagnostics panel lists last N reloads with timestamps, file path, format (JSON/TOML), and outcome.
1) Performance
• Default debounce 250ms (configurable) to avoid thrash on rapid saves.
• Precompiled JSON Schema.
• O(1) profile selection; O(n) merge when workspace override exists.
---
User Stories
• As a developer, I can switch profiles from IDE or CLI without restarting.
• As a CLI user, switching profiles updates the IDE state immediately.
• As a teammate, editing the JSON or TOML config hot-reloads safely; invalid edits roll back with clear errors.
---
Acceptance Criteria
• Editing config.json or config.toml triggers hot-reload within the debounce window across macOS/Linux/Windows.
• codex profile switch <name> updates activeProfile on disk (preserving JSON/TOML format) and both CLI/IDE use it immediately.
• Invalid edits do not break sessions; system rolls back and surfaces actionable diagnostics.
• Workspace override (if enabled) merges deterministically and is clearly indicated in UI.
• Env override CODEX_PROFILE takes precedence for the current session without persisting to disk.
---
Reproduction (Current Behavior)
- Start Codex IDE (and optionally a CLI session).
- Manually edit ~/.codex/config.toml or config.json (change activeProfile, model, etc.).
- Observe: IDE does not reload/apply changes; restart required.
Expected: IDE and CLI reflect changes without restart.
---
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗