Type of issue
Bug in VS Code's discovery of plugins installed by the GitHub Copilot CLI.
Upstream report: github/copilot-cli#4095
Related duplicate: github/copilot-cli#4151
Introducing VS Code change: #314805
Problem
On Windows, GitHub Copilot CLI plugin install, update, and uninstall can fail with:
Failed to install plugin: Error: Access is denied. (os error 5)
The upstream investigation enumerated open handles and found VS Code's file-watcher utility holding ReadDirectoryChangesW-style handles on the CLI-installed plugin root and component directories such as hooks, agents, and skills. Closing all VS Code windows releases the handles and the same CLI operation succeeds immediately.
VS Code causal chain
CopilotCliAgentPluginDiscovery scans:
~/.copilot/installed-plugins/<marketplace>/<plugin>/
It watches each marketplace directory and treats every immediate child directory as a plugin source, including CLI staging directories named like:
The shared AbstractAgentPluginDiscovery then creates non-recursive watchers on:
- the plugin root
commands
skills
agents
hooks
- other resolved component paths
Relevant code:
- CLI discovery and marketplace bucket watchers:
src/vs/workbench/contrib/chat/common/plugins/agentPluginServiceImpl.ts
- Plugin root and component watchers:
AbstractAgentPluginDiscovery._toPlugin in the same file
- Windows watcher backend:
src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts
This causes two related failure modes:
- Fresh installs can fail intermittently. For sufficiently large plugins, VS Code discovers the staging directory and opens watcher handles before the CLI performs its final rename. Small plugins can finish the rename before discovery reacts, making the bug appear size- or plugin-dependent.
- Updates and uninstalls can fail consistently. VS Code already has persistent watcher handles inside the final installed directory. The CLI cannot atomically rename/replace that directory on Windows, and VS Code cannot observe the successful parent-directory change that would cause it to rebuild or dispose those watchers.
The handle paths reported upstream match the watchers created by AbstractAgentPluginDiscovery exactly.
Steps to reproduce
-
On Windows, install a Copilot CLI plugin under ~/.copilot/installed-plugins/....
-
Open VS Code with Copilot/plugin discovery active and confirm the plugin is discovered.
-
While VS Code remains open, run:
copilot plugin update <plugin>@<marketplace>
The same problem can affect plugin install and plugin uninstall.
-
Observe Access is denied. (os error 5) during the final directory rename/replacement.
-
Close VS Code and repeat the operation; it succeeds.
Expected behavior
VS Code should allow the CLI to atomically install, replace, or remove CLI-owned plugin directories while still reflecting changes without requiring a reload.
Suggested direction
Avoid placing persistent watcher handles inside CLI-owned plugin directories. For example:
- watch the installed-plugins root or marketplace buckets from an ancestor, preferably recursively;
- use ancestor events to invalidate and rediscover affected plugins;
- do not create plugin-root/component watchers for
CopilotCliAgentPluginDiscovery sources;
- exclude CLI staging/tombstone directory names from discovery.
Filtering temporary directory names alone would reduce intermittent fresh-install failures but would not fix update/uninstall, because watchers on the existing final plugin directory would remain.
Validation needed
Add Windows coverage, or a focused watcher test where possible, proving that a discovered CLI plugin directory can be renamed/replaced while VS Code is running. Also cover staging directories so they are not surfaced as installed plugins.
Type of issue
Bug in VS Code's discovery of plugins installed by the GitHub Copilot CLI.
Upstream report: github/copilot-cli#4095
Related duplicate: github/copilot-cli#4151
Introducing VS Code change: #314805
Problem
On Windows, GitHub Copilot CLI plugin install, update, and uninstall can fail with:
The upstream investigation enumerated open handles and found VS Code's file-watcher utility holding
ReadDirectoryChangesW-style handles on the CLI-installed plugin root and component directories such ashooks,agents, andskills. Closing all VS Code windows releases the handles and the same CLI operation succeeds immediately.VS Code causal chain
CopilotCliAgentPluginDiscoveryscans:It watches each marketplace directory and treats every immediate child directory as a plugin source, including CLI staging directories named like:
The shared
AbstractAgentPluginDiscoverythen creates non-recursive watchers on:commandsskillsagentshooksRelevant code:
src/vs/workbench/contrib/chat/common/plugins/agentPluginServiceImpl.tsAbstractAgentPluginDiscovery._toPluginin the same filesrc/vs/platform/files/node/watcher/parcel/parcelWatcher.tsThis causes two related failure modes:
The handle paths reported upstream match the watchers created by
AbstractAgentPluginDiscoveryexactly.Steps to reproduce
On Windows, install a Copilot CLI plugin under
~/.copilot/installed-plugins/....Open VS Code with Copilot/plugin discovery active and confirm the plugin is discovered.
While VS Code remains open, run:
The same problem can affect
plugin installandplugin uninstall.Observe
Access is denied. (os error 5)during the final directory rename/replacement.Close VS Code and repeat the operation; it succeeds.
Expected behavior
VS Code should allow the CLI to atomically install, replace, or remove CLI-owned plugin directories while still reflecting changes without requiring a reload.
Suggested direction
Avoid placing persistent watcher handles inside CLI-owned plugin directories. For example:
CopilotCliAgentPluginDiscoverysources;Filtering temporary directory names alone would reduce intermittent fresh-install failures but would not fix update/uninstall, because watchers on the existing final plugin directory would remain.
Validation needed
Add Windows coverage, or a focused watcher test where possible, proving that a discovered CLI plugin directory can be renamed/replaced while VS Code is running. Also cover staging directories so they are not surfaced as installed plugins.