Concurrent `codex mcp remove` silently loses updates in ~/.codex/config.toml
What version of Codex CLI is running?
codex-cli 0.116.0-alpha.10
What subscription do you have?
ChatGPT account via Cursor-hosted Codex extension login. The exact plan is not surfaced by codex --version / codex login on this machine.
Which model were you using?
gpt-5.4
What platform is your computer?
Darwin 25.3.0 arm64 arm
What terminal emulator and version are you using (if applicable)?
Codex CLI binary bundled inside the Cursor extension (openai.chatgpt-26.318.11754-darwin-arm64), launched from a local shell on macOS.
What issue are you seeing?
codex mcp add / codex mcp remove appear to rewrite the full ~/.codex/config.toml without any cross-process locking or compare-and-swap protection.
When several codex mcp remove commands run in parallel against the same HOME, each process reports success (Removed global MCP server '...'), but the final config.toml keeps some of the supposedly removed MCP blocks.
This makes batch cleanup unsafe because the CLI reports success even though the final config is only partially updated.
Initially this looked like an OAuth-specific problem because I first observed it while removing notion and sentry, but after isolating the repro I could reproduce the same lost-update behavior with mixed MCP entries. Running the same removals sequentially works correctly.
What steps can reproduce the bug?
Use a disposable HOME so this does not touch your real Codex config:
tmp=$(mktemp -d)
mkdir -p "$tmp/.codex"
cat > "$tmp/.codex/config.toml" <<'CONFIG'
model = "gpt-5.4"
model_reasoning_effort = "xhigh"
[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp@latest"]
[mcp_servers.figma]
url = "https://mcp.figma.com/mcp"
[mcp_servers.linear]
url = "https://mcp.linear.app/mcp"
[mcp_servers.notion]
url = "https://mcp.notion.com/mcp"
[mcp_servers.playwright]
command = "npx"
args = ["-y", "@playwright/mcp@latest"]
[mcp_servers.sentry]
url = "https://mcp.sentry.dev/mcp"
[features]
multi_agent = true
CONFIG
HOME="$tmp" codex mcp remove figma &
HOME="$tmp" codex mcp remove notion &
HOME="$tmp" codex mcp remove playwright &
HOME="$tmp" codex mcp remove sentry &
wait
cat "$tmp/.codex/config.toml"
Observed result on my machine:
- every process prints
Removed global MCP server '...' - the final
config.tomlstill contains some offigma,notion,playwright,sentry - the exact survivors depend on write timing, which looks like last-writer-wins behavior
Control case:
- if I run the same
codex mcp remove ...commands sequentially, all target MCP entries are removed correctly
What is the expected behavior?
One of these should happen:
- Codex serializes config writes so concurrent
mcp add/removeoperations are safe. - Codex detects that
config.tomlchanged since read and retries / fails explicitly. - Codex at minimum returns a non-zero exit code instead of reporting success when another process has overwritten its change.
The CLI should not silently lose some removals while still printing a success message for every command.
Additional information
- I found the issue while cleaning up MCP servers from a shared Codex profile.
- Sequential mutation is a viable workaround, but it is easy to assume parallel cleanup is safe because the CLI reports success.
- I did not need OAuth login to reproduce the core bug once I switched to the disposable-HOME repro above.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗