Repository navigation
Description
MCP settings wiped when multiple windows open — race in McpHub.getMcpSettingsFilePath() direct fs.writeFile
Summary
Opening multiple VS Code windows concurrently can wipe mcp_settings.json to a 122-byte stub {"mcpServers": {}}, losing all MCP server configurations. Root cause is a race in McpHub.getMcpSettingsFilePath() which uses direct fs.writeFile without advisory lock, bypassing the existing safeWriteJson utility.
Environment
- Extension:
zoocodeorganization.zoo-codev3.80.0 - OS: Linux (generic)
- VS Code: multiple windows / workspaces open simultaneously
Steps to Reproduce
- Have a populated
mcp_settings.jsonat~/.config/Code/User/globalStorage/zoocodeorganization.zoo-code/settings/mcp_settings.json(or equivalent globalStorage path) with severalmcpServersentries. - Open 2+ VS Code windows at roughly the same time (e.g.,
code .in two workspaces, or restoring session with multiple windows). - Each window constructs
McpHub, which callsgetMcpSettingsFilePath()→ensureSettingsDirectoryExists()→fileExistsAtPath()→fs.writeFileif not exists. - Observe file truncated to 122 bytes.
Observed Behavior
- File
mcp_settings.jsonbecomes:(122 bytes, pretty-printed empty stub){ "mcpServers": { } } statshowsBirth == Modify == 07:54(file recreated at that time), indicating overwrite rather than edit.FileSystemWatcher(watchMcpSettingsFile) silently accepts the stub as valid config and propagates empty server list to all windows viadebounceConfigChange→updateServerConnections({}, "global").- All MCPs disappear from UI; no error shown (empty object passes
McpSettingsSchemavalidation).
Expected Behavior
- Concurrent initializations should not clobber existing config.
- File creation should be atomic and locked, preserving existing servers.
Root Cause
File: src/services/mcp/McpHub.ts:496-517 (getMcpSettingsFilePath())
const fileExists = await fileExistsAtPath(mcpSettingsFilePath)
if (!fileExists) {
await fs.writeFile(
mcpSettingsFilePath,
`{
"mcpServers": {
}
}`,
)
}- Uses direct
fs.writeFilewithout lock, whilesrc/utils/safeWriteJson.ts:45exists and is already used elsewhere in same file (lines 2073, 2158, 2367) for other config writes. safeWriteJsonprovidesproper-lockfileadvisory lock (stale 31s, 5 retries, exponential backoff), temp file + atomic rename, and backup/rollback. This path bypasses it.- TOCTOU: two processes both see
fileExists==false(or one checks while other is mid-write), second blind write wins and truncates. - No merge guard: even if file was just created by another window with content, it is overwritten with empty stub.
Watcher: watchMcpSettingsFile() at McpHub.ts:519 debounces and calls handleConfigFileChange which does JSON.parse + McpSettingsSchema.safeParse — empty mcpServers is valid, so no error is surfaced.
Impact
- Data loss: All MCP server definitions wiped (API keys, commands, URLs, env). User must restore from backup.
- Silent: No error toast; user discovers only when MCPs missing.
- Reproducible: Race window is small but reliably hit when opening multiple windows on session restore or via CLI.
Proposed Fix
Replace direct write with safeWriteJson:
- await fs.writeFile(
- mcpSettingsFilePath,
- `{
- "mcpServers": {
-
- }
- }`,
- )
+ await safeWriteJson(mcpSettingsFilePath, { mcpServers: {} }, { prettyPrint: true })- Import already exists:
import { safeWriteJson } from "../../utils/safeWriteJson"atMcpHub.ts:44. safeWriteJsonhandles lock, atomic write, dir creation.
Optional stronger guard (handles TOCTOU between fileExistsAtPath and lock):
await safeWriteJson(mcpSettingsFilePath, { mcpServers: {} }, {
prettyPrint: true,
merge: (existing, incoming) => {
if (existing && typeof existing === "object" && "mcpServers" in existing) {
const ex = existing as { mcpServers?: Record<string, unknown> }
if (ex.mcpServers && Object.keys(ex.mcpServers).length > 0) return existing
}
return incoming
}
})This makes it atomic read-modify-write under lock.
Patch prepared against v3.80.0 tag; source-only (installed dist/extension.js is minified, not patched). Patch file available on request.
Workaround Until Fixed
- Open single window at a time, or stagger window opens by a few seconds.
- Keep manual backup: e.g.,
~/Desktop/CODE/ZooMCPBakup/mcp_settings.json(or any versioned backup outside globalStorage). - Restore via
cp ~/Desktop/CODE/ZooMCPBakup/mcp_settings.json ~/.config/Code/User/globalStorage/zoocodeorganization.zoo-code/settings/mcp_settings.jsonand reload window.
Additional Context
- No duplicate found for
mcp_settings.json race OR McpHub race OR mcp wipe(searched viagh issue list). - File references:
McpHub.ts:496,safeWriteJson.ts:45,GlobalFileNames.mcpSettings. - Willing to submit PR with above patch.
Checklist
- Searched existing issues — no duplicate
- Anonymized paths (no personal info)
- Includes code refs and proposed fix
Activity
fix(mcp): preserve concurrent MCP settings during initial creation (f…