fix(mcp): preserve concurrent MCP settings during initial creation (fixes #1371) - #1380
Conversation
…ixes Zoo-Code-Org#1371) getMcpSettingsFilePath() created the default mcp_settings.json with a check-then-write: fileExistsAtPath() followed by an unconditional fs.writeFile of the empty stub. Two windows racing at startup both saw the file as absent, and the second blind write truncated the first window's config to the 122-byte stub. The stub write now goes through safeWriteJson with a merge callback: the read happens under the advisory lock, and any config already on disk (written by a concurrent process after the existence check) is preserved instead of clobbered. The fast path (file exists -> no write) is unchanged, so no watcher-triggered reloads or write amplification. Test: regression test reproduces the interleaving (existence check sees absent file, locked read sees the concurrent config) and asserts the creation write carries the concurrent config, not the stub. The safeWriteJson spec mock now honors options.merge.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review. 📜 Recent review details
|
| Layer / File(s) | Summary |
|---|---|
Locked settings creation src/services/mcp/McpHub.ts |
getMcpSettingsFilePath uses a merge callback that preserves non-array object values for mcpServers. Missing, non-object, and array values use { mcpServers: {} }. |
Settings creation validation src/services/mcp/__tests__/McpHub.spec.ts, src/services/mcp/__tests__/McpHub.settingsCreation.integration.spec.ts |
Tests model recoverable and non-recoverable read failures. Unit and integration tests cover concurrent configuration preservation, default stub creation, invalid values, advisory locking, cleanup, and EACCES handling. |
Priority: ⬆️ High
Estimated code review effort: 3 (Moderate) | ~25 minutes
Change: Bug fix · Severity of issue fixed: High
Sequence Diagram(s)
sequenceDiagram
participant McpHub
participant safeWriteJson
participant Lock
participant SettingsFile
McpHub->>safeWriteJson: create settings with merge callback
safeWriteJson->>Lock: acquire advisory lock
Lock->>SettingsFile: read existing configuration
SettingsFile-->>safeWriteJson: return configuration
safeWriteJson->>SettingsFile: preserve valid config or write empty mcpServers
Merge Risk: ⚪ Minimal · up to 35194
The change preserves concurrently written MCP server settings during initialization while retaining default creation and error handling behavior. No unresolved merge-blocking risk was identified.
🚥 Pre-merge checks | ✅ 7
✅ Passed checks (7 passed)
| Check name | Status | Explanation |
|---|---|---|
| Linked Issues check | ✅ Passed | Issue #1371 requires atomic, lock-protected initial creation that does not overwrite MCP servers written after the existence check. getMcpSettingsFilePath() now calls safeWriteJson with a merge ca… |
| Out of Scope Changes check | ✅ Passed | The changed production code addresses the linked race directly. The mock update and added unit and integration tests support the merge contract and regression coverage for Issue #1371. No unrelated pr… |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 3… |
| Regression Evidence | ✅ Passed | PASS. The changed creation path has focused coverage. McpHub.spec.ts covers concurrent config preservation, absent content, missing mcpServers, non-object values, arrays, and locked-read EACCES failur… |
| Trust And Persistence Invariants | ✅ Passed | No changed path meets a stated failure condition. McpHub.getMcpSettingsFilePath() now awaits safeWriteJson with advisory locking, locked read/merge, temporary-file output, and atomic rename. The m… |
| Title check | ✅ Passed | The title clearly identifies the MCP race-condition fix and the preservation of concurrent settings. It is concise and directly related to the main change. |
| Description check | ✅ Passed | The description explains the issue, implementation, testing, and linked issue. It is mostly complete, although it does not use all template sections, such as the pre-submission checklist and reviewer … |
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
- Create stacked PR
- Commit on current branch
🧪 Generate unit tests (beta)
- Create PR with unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Codecov reported 2 patch lines (1 missing, 1 partial) in the safeWriteJson merge callback. Add the three remaining fallback cases: absent file (merge sees null), existing content without an mcpServers object, and mcpServers present but not an object - all must write the default stub. All changed lines and branches of the merge callback are now covered.
|
Patch coverage gap (Codecov: 2 lines, 1 missing + 1 partial in the merge callback) addressed in 332253a: added the three remaining fallback cases of getMcpSettingsFilePath (absent file, existing content without an mcpServers object, and mcpServers present but not an object). All changed lines and all merge-callback branches are now covered locally (v8 lcov: lines 519/520/521/523 all hit; BRDA outcomes all > 0); 67/67 McpHub spec tests pass. |
…, unknown-safe code read The mcp_settings merge callback now requires a plain object (!Array.isArray), so an existing mcpServers: [] is replaced by the empty stub instead of being preserved and later rejected by McpSettingsSchema; the safeWriteJson spec mock mirrors the production merge contract (only ENOENT and SyntaxError are recoverable, any other read failure rejects before the merge callback runs, with an EACCES regression); the error.code read in the mock uses an unknown-safe type guard instead of an object cast. (CodeRabbit findings on trial Zoo-Code-Org#1413).
Review statusThanks for contributing. This comment tracks the review sequence and the next action. Current step: Awaiting fresh human maintainer or CODEOWNER approval. Automated review is complete for the latest commit but does not replace human approval. Review-state labels are managed by this workflow; do not edit them manually. |
…ad review gate (no code change)
…settings-stub-race-1371
Summary
Fixes #1371 (part of epic #1375).
MCP settings initialization has a check-then-create race:
getMcpSettingsFilePath()didfileExistsAtPath()followed by an unconditionalfs.writeFileof the empty stub. Two VS Code windows (separate extension-host processes) racing at startup both see the file as absent, and the second blind write truncates the first window's config to the 122-byte stub — silent data loss of all configured MCP servers.Changes
src/services/mcp/McpHub.ts— the stub creation ingetMcpSettingsFilePath()now goes throughsafeWriteJsonwith amergecallback:mcpServersobject, it is preserved; otherwise the default stub is written;src/services/mcp/__tests__/McpHub.spec.ts:fs.writeFileimplementation.)safeWriteJsonspec mock now honorsoptions.merge(it previously ignored options, which masked merge semantics for every test in this file).Notes
- No suppression count changes;
This is an auto-generated comment: release notes by coderabbit.ai -->safeWriteJsonwas already the established pattern for this file (used by the add/remove-server flows); this change removes the one remaining rawfs.writeFileinMcpHub.eslint --max-warnings=0clean on both touched files.pnpm --dir src exec vitest run services/mcp/__tests__/McpHub.spec.ts→ 64/64 passing.Summary by CodeRabbit
- Bug Fixes
- Improved reliability when creating MCP settings.
- Preserves settings added concurrently instead of overwriting them.
end of auto-generated comment: release notes by coderabbit.ai -->Update (CodeRabbit-sync from trial #1413): head
9dd9825c4— mcp_settings merge array guard, spec-mock production error parity, unknown-safe error.code read (trial addenda 178e6f4 + 4fc14c4). Review context: trial PR #1413.Review-gate re-trigger (2026-08-30): empty commit 046b78c (no code change) re-runs CI and CodeRabbit current-head review under the org new PR review gate; the code head remains 9dd9825.