[BUG] Admin account plugin reconciliation kills custom MCP servers via race condition
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
<html>
<body>
<!--StartFragment--><h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Summary</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">On Claude Desktop for Windows, custom MCP servers defined in <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">claude_desktop_config.json</code> are killed and fail to reconnect after the app's plugin reconciliation cycle. This affects <strong>only admin accounts</strong> in an organization. Non-admin accounts on the same org, same app version, same config, same machine type work perfectly.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Environment</h2>
<ul class="[li_&]:mb-0 [li_&]:mt-1 [li_&]:gap-1 [&:not(:last-child)_ul]:pb-1 [&:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2"><strong>App version</strong>: 1.1.9134 (also reproduced on 1.1.8629)</li>
<li class="whitespace-normal break-words pl-2"><strong>OS</strong>: Windows 10/11</li>
<li class="whitespace-normal break-words pl-2"><strong>MCP SDK</strong>: Python <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">mcp</code> 1.26.0</li>
<li class="whitespace-normal break-words pl-2"><strong>Organization</strong>: Team plan with 18 org-level plugins (Anthropic & Partners defaults, all "Available for install", none actually installed)</li>
<li class="whitespace-normal break-words pl-2"><strong>Account role</strong>: Organization admin</li>
</ul>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Reproduction steps</h2>
<ol class="[li_&]:mb-0 [li_&]:mt-1 [li_&]:gap-1 [&:not(:last-child)_ul]:pb-1 [&:not(:last-child)_ol]:pb-1 list-decimal flex flex-col gap-1 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2">Create an organization on Claude Team plan</li>
<li class="whitespace-normal break-words pl-2">Log into Claude Desktop with an <strong>admin</strong> account</li>
<li class="whitespace-normal break-words pl-2">Configure two custom MCP servers in <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">claude_desktop_config.json</code>:
<ul class="[li_&]:mb-0 [li_&]:mt-1 [li_&]:gap-1 [&:not(:last-child)_ul]:pb-1 [&:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2">One Node.js server (e.g., <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">"command": "node"</code>)</li>
<li class="whitespace-normal break-words pl-2">One Python server (e.g., <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">"command": "python.exe"</code>)</li>
</ul>
</li>
<li class="whitespace-normal break-words pl-2">Launch Claude Desktop</li>
<li class="whitespace-normal break-words pl-2">Both servers connect successfully during initial startup</li>
<li class="whitespace-normal break-words pl-2">Within 1–2 seconds, the plugin reconciliation cycle runs</li>
<li class="whitespace-normal break-words pl-2">The Python server is killed and fails to reconnect</li>
<li class="whitespace-normal break-words pl-2">The Node.js server survives</li>
</ol>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>Same config on a non-admin account</strong>: both servers survive every time.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Root cause analysis</h2>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold">The kill sequence (from <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">main.log</code>)</h3>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Admin account:</p>
<div role="group" aria-label="Codice" tabindex="0" class="relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100"><div class="sticky opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 top-2 py-2 h-12 w-0 float-right"><div class="absolute right-0 h-8 px-2 items-center inline-flex z-10"><button class="inline-flex
items-center
justify-center
relative
isolate
shrink-0
can-focus
select-none
disabled:pointer-events-none
disabled:opacity-50
disabled:shadow-none
disabled:drop-shadow-none border-transparent
transition
font-base
duration-300
ease-[cubic-bezier(0.165,0.85,0.45,1)] h-8 w-8 rounded-md backdrop-blur-md _fill_56vq7_9 _ghost_56vq7_96" type="button" aria-label="Copia negli appunti" data-state="closed"><div class="relative"><div class="transition-all opacity-100 scale-100" style="width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="transition-all opacity-100 scale-100" aria-hidden="true" style="flex-shrink: 0;"><path d="M12.5 3A1.5 1.5 0 0 1 14 4.5V6h1.5A1.5 1.5 0 0 1 17 7.5v8a1.5 1.5 0 0 1-1.5 1.5h-8A1.5 1.5 0 0 1 6 15.5V14H4.5A1.5 1.5 0 0 1 3 12.5v-8A1.5 1.5 0 0 1 4.5 3zm1.5 9.5a1.5 1.5 0 0 1-1.5 1.5H7v1.5a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5H14zM4.5 4a.5.5 0 0 0-.5.5v8a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5z"></path></svg></div><div class="absolute inset-0 flex items-center justify-center"><div class="transition-all opacity-0 scale-50" style="width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="transition-all opacity-0 scale-50" aria-hidden="true" style="flex-shrink: 0;"><path d="M15.188 5.11a.5.5 0 0 1 .752.626l-.056.084-7.5 9a.5.5 0 0 1-.738.033l-3.5-3.5-.064-.078a.501.501 0 0 1 .693-.693l.078.064 3.113 3.113 7.15-8.58z"></path></svg></div></div></div></button></div></div><div class="overflow-x-auto"><pre class="code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed p-3.5" style="color: rgb(20, 24, 31); background: transparent; font-family: var(--font-mono);"><code style="color: rgb(20, 24, 31); background: transparent; font-family: var(--font-mono); white-space: pre-wrap;"><span><span>17:07:58 Launching MCP Server: normattiva ← initial boot, both OK
</span></span><span>17:07:58 Launching MCP Server: italgiure
</span><span>17:07:59 [PluginsFetcher] fetchBrowsableRemotePlugins: 18 plugins
</span><span>17:07:59 [CustomPlugins] Merged 0 local + 18 remote = 18 total plugins
</span><span>17:08:00 Launching MCP Server: italgiure ← re-launched after merge
</span><span>17:08:00 Shutting down MCP Server: italgiure ← immediately killed
</span><span>17:08:00 UtilityProcess Check: Extension italgiure not found in installed extensions</span></code></pre></div></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Non-admin account (same org, same config, same app version):</p>
<div role="group" aria-label="Codice" tabindex="0" class="relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100"><div class="sticky opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 top-2 py-2 h-12 w-0 float-right"><div class="absolute right-0 h-8 px-2 items-center inline-flex z-10"><button class="inline-flex
items-center
justify-center
relative
isolate
shrink-0
can-focus
select-none
disabled:pointer-events-none
disabled:opacity-50
disabled:shadow-none
disabled:drop-shadow-none border-transparent
transition
font-base
duration-300
ease-[cubic-bezier(0.165,0.85,0.45,1)] h-8 w-8 rounded-md backdrop-blur-md _fill_56vq7_9 _ghost_56vq7_96" type="button" aria-label="Copia negli appunti" data-state="closed"><div class="relative"><div class="transition-all opacity-100 scale-100" style="width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="transition-all opacity-100 scale-100" aria-hidden="true" style="flex-shrink: 0;"><path d="M12.5 3A1.5 1.5 0 0 1 14 4.5V6h1.5A1.5 1.5 0 0 1 17 7.5v8a1.5 1.5 0 0 1-1.5 1.5h-8A1.5 1.5 0 0 1 6 15.5V14H4.5A1.5 1.5 0 0 1 3 12.5v-8A1.5 1.5 0 0 1 4.5 3zm1.5 9.5a1.5 1.5 0 0 1-1.5 1.5H7v1.5a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5H14zM4.5 4a.5.5 0 0 0-.5.5v8a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5z"></path></svg></div><div class="absolute inset-0 flex items-center justify-center"><div class="transition-all opacity-0 scale-50" style="width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="transition-all opacity-0 scale-50" aria-hidden="true" style="flex-shrink: 0;"><path d="M15.188 5.11a.5.5 0 0 1 .752.626l-.056.084-7.5 9a.5.5 0 0 1-.738.033l-3.5-3.5-.064-.078a.501.501 0 0 1 .693-.693l.078.064 3.113 3.113 7.15-8.58z"></path></svg></div></div></div></button></div></div><div class="overflow-x-auto"><pre class="code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed p-3.5" style="color: rgb(20, 24, 31); background: transparent; font-family: var(--font-mono);"><code style="color: rgb(20, 24, 31); background: transparent; font-family: var(--font-mono); white-space: pre-wrap;"><span><span>10:56:26 Launching MCP Server: normattiva ← initial boot, both OK
</span></span><span>10:56:26 Launching MCP Server: italgiure
</span><span>10:56:27 [CustomPlugins] Merged 0 local + 18 remote = 18 total plugins
</span><span>10:56:28 Launching MCP Server: italgiure ← re-launched after merge
</span><span>10:56:28 Shutting down MCP Server: italgiure ← killed here too...
</span><span> ← ...but italgiure SURVIVES in UI</span></code></pre></div></div>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold">The race condition</h3>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Both admin and non-admin accounts experience the same Launching→Shutting down cycle after the plugin merge. However:</p>
<ul class="[li_&]:mb-0 [li_&]:mt-1 [li_&]:gap-1 [&:not(:last-child)_ul]:pb-1 [&:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2"><strong>Non-admin</strong>: the server completes its MCP handshake (<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">initialize</code> → <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">tools/list</code>) before or during the kill, and the UI retains the connection</li>
<li class="whitespace-normal break-words pl-2"><strong>Admin</strong>: the admin account performs additional checks during reconciliation (<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">blocklist fetch</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">allowlist check</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">can_install check</code>) that shift the timing by ~500ms, causing the kill to land <em>after</em> the handshake starts but <em>before</em> it completes</li>
</ul>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">This is a classic race condition. The plugin reconciliation and the MCP server handshake compete for the same internal state, and the admin-specific checks shift the timing just enough to break it.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold">Evidence it's timing-dependent</h3>
<ul class="[li_&]:mb-0 [li_&]:mt-1 [li_&]:gap-1 [&:not(:last-child)_ul]:pb-1 [&:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2">The Node.js server (<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">normattiva</code>) <strong>always survives</strong> — it completes initialization in <100ms</li>
<li class="whitespace-normal break-words pl-2">The Python server (<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">italgiure</code>) <strong>always dies</strong> — it takes 1–2 seconds to initialize</li>
<li class="whitespace-normal break-words pl-2">On the non-admin account, the slightly different timing allows the Python server to complete its handshake before the kill lands</li>
<li class="whitespace-normal break-words pl-2">The <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">mcp.log</code> shows the Python server successfully completes the full handshake (<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">initialize</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">notifications/initialized</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">tools/list</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">prompts/list</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">resources/list</code> — all OK), but the app's UI doesn't reflect it</li>
</ul>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold">From <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">mcp.log</code> (admin account)</h3>
<div role="group" aria-label="Codice" tabindex="0" class="relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100"><div class="sticky opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 top-2 py-2 h-12 w-0 float-right"><div class="absolute right-0 h-8 px-2 items-center inline-flex z-10"><button class="inline-flex
items-center
justify-center
relative
isolate
shrink-0
can-focus
select-none
disabled:pointer-events-none
disabled:opacity-50
disabled:shadow-none
disabled:drop-shadow-none border-transparent
transition
font-base
duration-300
ease-[cubic-bezier(0.165,0.85,0.45,1)] h-8 w-8 rounded-md backdrop-blur-md _fill_56vq7_9 _ghost_56vq7_96" type="button" aria-label="Copia negli appunti" data-state="closed"><div class="relative"><div class="transition-all opacity-100 scale-100" style="width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="transition-all opacity-100 scale-100" aria-hidden="true" style="flex-shrink: 0;"><path d="M12.5 3A1.5 1.5 0 0 1 14 4.5V6h1.5A1.5 1.5 0 0 1 17 7.5v8a1.5 1.5 0 0 1-1.5 1.5h-8A1.5 1.5 0 0 1 6 15.5V14H4.5A1.5 1.5 0 0 1 3 12.5v-8A1.5 1.5 0 0 1 4.5 3zm1.5 9.5a1.5 1.5 0 0 1-1.5 1.5H7v1.5a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5H14zM4.5 4a.5.5 0 0 0-.5.5v8a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5z"></path></svg></div><div class="absolute inset-0 flex items-center justify-center"><div class="transition-all opacity-0 scale-50" style="width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="transition-all opacity-0 scale-50" aria-hidden="true" style="flex-shrink: 0;"><path d="M15.188 5.11a.5.5 0 0 1 .752.626l-.056.084-7.5 9a.5.5 0 0 1-.738.033l-3.5-3.5-.064-.078a.501.501 0 0 1 .693-.693l.078.064 3.113 3.113 7.15-8.58z"></path></svg></div></div></div></button></div></div><div class="overflow-x-auto"><pre class="code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed p-3.5" style="color: rgb(20, 24, 31); background: transparent; font-family: var(--font-mono);"><code style="color: rgb(20, 24, 31); background: transparent; font-family: var(--font-mono); white-space: pre-wrap;"><span><span>15:45:24.548Z [italgiure] Message from server: initialize response OK
</span></span><span>15:45:24.643Z [italgiure] Message from client: notifications/initialized
</span><span>15:45:24.644Z [italgiure] Message from client: tools/list
</span><span>15:45:24.650Z [italgiure] Message from server: tools list (10 tools) OK
</span><span>15:45:24.655Z [italgiure] Message from server: prompts list OK
</span><span>15:45:24.656Z [italgiure] Message from server: resources list OK
</span><span>← Server is fully initialized, all capabilities exchanged
</span><span>← But UI shows server as disconnected</span></code></pre></div></div>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">What was tried (all failed)</h2>
<div class="overflow-x-auto w-full px-2 mb-6">
Attempt | Result
-- | --
Set coworkScheduledTasksEnabled: false + ccdScheduledTasksEnabled: false | Worked temporarily, app overwrites values back to true
Set config file to read-only | App can't save other preferences
Wrap Python server behind Node.js proxy | Timeout — proxy parsing issue, but even fixing it wouldn't help since the kill is name-based
Rename server from italgiure to ced-cassazione | Still killed
Set isUsingBuiltInNodeForMcp: false | No effect
Uninstall PDF Tools marketplace extension | No effect (was a red herring)
Full reinstall of Claude Desktop | No effect
Delete entire %APPDATA%\Claude folder | Worked for first boot only, problem returns on second boot
Disable "Abilita connettori artefatto" org setting | No effect on plugin fetch
</div>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Expected behavior</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Custom MCP servers defined in <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">claude_desktop_config.json</code> should not be affected by the plugin reconciliation cycle. The reconciliation should only manage marketplace/org plugins, not stdio-based custom servers.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Alternatively, if the reconciliation must restart custom servers, it should wait for the MCP handshake to complete before considering the server disconnected.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Suggested fix</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The plugin reconciliation cycle should either:</p>
<ol class="[li_&]:mb-0 [li_&]:mt-1 [li_&]:gap-1 [&:not(:last-child)_ul]:pb-1 [&:not(:last-child)_ol]:pb-1 list-decimal flex flex-col gap-1 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2"><strong>Skip custom servers</strong> (<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">claude_desktop_config.json</code> entries) entirely — they are not "extensions" and should not be part of the reconciliation</li>
<li class="whitespace-normal break-words pl-2"><strong>Gate the reconnection</strong> — after restarting a server, wait for the full MCP handshake (<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">initialize</code> → <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">notifications/initialized</code> → <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">tools/list</code>) before processing the next reconciliation step</li>
<li class="whitespace-normal break-words pl-2"><strong>Remove the timing dependency</strong> — decouple the plugin merge from the MCP server lifecycle so they don't race</li>
</ol>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Workaround</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The only reliable workaround we found: after each app launch, manually trigger a reconnection of the affected server (e.g., via the UI if available, or by editing and re-saving <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">claude_desktop_config.json</code>). This is not practical for daily use.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Additional context</h2>
<ul class="[li_&]:mb-0 [li_&]:mt-1 [li_&]:gap-1 [&:not(:last-child)_ul]:pb-1 [&:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2">This was debugged over ~4 hours with full log capture on both admin and non-admin accounts</li>
<li class="whitespace-normal break-words pl-2">The org has 18 default Anthropic & Partners plugins, all set to "Available for install" (none actually installed by any user)</li>
<li class="whitespace-normal break-words pl-2">The problem does not occur on macOS with the same account and config</li>
<li class="whitespace-normal break-words pl-2">The problem appeared around February 2026 and persists through v1.1.9134</li></ul><!--EndFragment-->
</body>
</html>
What Should Happen?
Custom MCP servers defined in claude_desktop_config.json should not be killed by the plugin reconciliation cycle. The reconciliation should only manage marketplace/org plugins, not stdio-based custom servers. If servers must be restarted, the app should wait for the MCP handshake to complete before processing the next step.
Error Messages/Logs
ADMIN ACCOUNT (server killed):
17:07:58 Launching MCP Server: normattiva
17:07:58 Launching MCP Server: italgiure
17:07:59 [PluginsFetcher] fetchBrowsableRemotePlugins: 18 plugins
17:07:59 [CustomPlugins] Merged 0 local + 18 remote = 18 total plugins
17:08:00 Launching MCP Server: italgiure
17:08:00 Shutting down MCP Server: italgiure
17:08:00 [warn] UtilityProcess Check: Extension italgiure not found in installed extensions
NON-ADMIN ACCOUNT (same org, same config, server survives):
10:56:26 Launching MCP Server: normattiva
10:56:26 Launching MCP Server: italgiure
10:56:27 [CustomPlugins] Merged 0 local + 18 remote = 18 total plugins
10:56:28 Launching MCP Server: italgiure
10:56:28 Shutting down MCP Server: italgiure
(italgiure remains connected in UI despite shutdown log)
Steps to Reproduce
- Create a Claude Team organization
- Log in to Claude Desktop (Windows) with an organization ADMIN account
- Add a Python-based custom MCP server to claude_desktop_config.json:
{
"mcpServers": {
"my-server": {
"command": "python.exe",
"args": ["server.py"],
"env": {}
}
}
}
- Launch Claude Desktop
- Server connects successfully on initial boot
- Plugin reconciliation runs (~1-2 seconds after boot):
[CustomPlugins] Merged 0 local + 18 remote = 18 total plugins
- Server is killed and fails to reconnect
- Repeat with a NON-ADMIN account on same org: server survives
Note: Node.js servers survive because they complete the handshake faster (<100ms) than Python servers (1-2s). The race condition only manifests with slower-starting servers.
Claude Model
Sonnet (default)
Is this a regression?
Yes, this worked in a previous version
Last Working Version
Before February 2026 updates
Claude Code Version
N/A — this is a Claude Desktop bug, not Claude
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
Detailed analysis
This bug was debugged over ~4 hours with full log capture on both admin and non-admin accounts in the same organization.
The race condition
Both admin and non-admin accounts experience the same plugin reconciliation cycle after boot. The app fetches 18 org-level plugins (Anthropic & Partners defaults), merges them, then re-launches custom MCP servers. During this re-launch:
- Node.js servers (e.g., normattiva) complete the MCP handshake in <100ms and survive
- Python servers (e.g., italgiure) take 1-2 seconds to initialize and get killed mid-handshake
On non-admin accounts, the timing is slightly different (no blocklist/allowlist/can_install checks), allowing the Python server to complete its handshake before the kill lands. On admin accounts, the additional checks shift timing by ~500ms, causing the kill to land during the handshake.
What was tried (all failed)
| Attempt | Result |
|---------|--------|
| Set coworkScheduledTasksEnabled/ccdScheduledTasksEnabled to false | App overwrites values back to true on restart |
| Set config file to read-only | App can't save other preferences |
| Wrap Python server behind Node.js stdio proxy | Doesn't help — kill is triggered by plugin reconciliation, not by server type |
| Rename server in config | Still killed |
| Set isUsingBuiltInNodeForMcp: false | No effect |
| Uninstall all marketplace extensions | No effect — 18 org plugins still fetched |
| Full reinstall of Claude Desktop | No effect |
| Delete entire %APPDATA%\Claude folder | First boot works, problem returns on second boot |
| Disable org-level UI toggles | No effect on plugin fetch |
Key evidence
The org has 18 default Anthropic & Partners plugins, all set to "Available for install" (none actually installed). The fetch happens because the account is an org admin. Non-admin accounts in the same org, same app version, same config do NOT experience the server kill.
The mcp.log confirms the Python server completes its full MCP handshake successfully (initialize, notifications/initialized, tools/list, prompts/list, resources/list — all OK), but the app's UI shows it as disconnected.
Suggested fix
- Skip custom servers (claude_desktop_config.json entries) during plugin reconciliation — they are not extensions
- Or: after restarting a server, wait for the MCP handshake to complete before processing the next reconciliation step
- Or: decouple the plugin merge from the MCP server lifecycle entirely
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗