#18007·theia

Local plugin directory resolver treats .DS_Store as a plugin, logging 'Failed to load plugin dependencies ... ENOTDIR' on every start

Author: DisasterAreaDesignsCreated Sep 9, 2026Updated Sep 14, 2026
LabelsbugOS/Macplug-in system

Bug Description

LocalDirectoryPluginDeployerResolver.resolveFromLocalPath treats every entry in the directory as a plugin, including dotfiles. On macOS the Finder drops a .DS_Store into any folder a user has ever looked at, so a local-dir: plugin directory the user has opened produces this on every application start:

root ERROR Failed to load plugin dependencies from '/Users/…/plugins/.DS_Store': Error: ENOTDIR: not a directory, open '/Users/…/plugins/.DS_Store/package.json'

The error is harmless — the real plugins still deploy — but it is alarming, permanent, and sends people looking for a broken plugin that does not exist.

Steps to Reproduce

  1. Point the app at a local plugin directory, e.g. THEIA_PLUGINS=local-dir:/path/to/plugins.
  2. Open that directory in Finder once (or touch /path/to/plugins/.DS_Store).
  3. Start the app and read the backend log.

Expected

No error. Dot-prefixed entries are not plugins and should be ignored.

Actual

Failed to load plugin dependencies … ENOTDIR on every start, once per dotfile.

Cause

packages/plugin-ext/src/main/node/resolvers/local-directory-plugin-deployer-resolver.ts:

typescript
protected async resolveFromLocalPath(pluginResolverContext: PluginDeployerResolverContext, localPath: string): Promise<void> {
    const files = await fs.readdir(localPath);
    files.forEach(file =>
        pluginResolverContext.addPlugin(file, path.resolve(localPath, file))
    );
}

Every name is added unconditionally, so .DS_Store becomes a plugin id and the deployer later tries to read .DS_Store/package.json.

Suggested Fix

Skip dot-prefixed entries. A plugin directory never begins with a dot, so nothing is lost:

diff
 protected async resolveFromLocalPath(pluginResolverContext: PluginDeployerResolverContext, localPath: string): Promise<void> {
     const files = await fs.readdir(localPath);
-    files.forEach(file =>
-        pluginResolverContext.addPlugin(file, path.resolve(localPath, file))
-    );
+    files.forEach(file => {
+        if (file.startsWith('.')) {
+            // e.g. the .DS_Store the macOS Finder drops into any folder it displays
+            return;
+        }
+        pluginResolverContext.addPlugin(file, path.resolve(localPath, file));
+    });
 }

This is what we ended up shipping downstream as a subclass override, and it removes the error without affecting plugin discovery.

Same shape of noise appears for any other stray dotfile in the directory (._* AppleDouble files from a copy off a non-HFS volume, for instance).

Versions

Happy to open a PR if useful — I have not signed the ECA yet, so I have filed this as an issue rather than let a PR sit blocked on validation.