From f44b651cd6752053e175301c8c8f911b1be882fc Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Sat, 30 May 2026 14:57:17 -0400 Subject: [PATCH] fix(management): skip broken shared plugin entries --- src/management/shared-manager.ts | 14 +++++++++++++- tests/unit/shared-manager.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/management/shared-manager.ts b/src/management/shared-manager.ts index 6ce5b2ce..728fa0e9 100644 --- a/src/management/shared-manager.ts +++ b/src/management/shared-manager.ts @@ -354,7 +354,19 @@ class SharedManager { } const entryPath = path.join(sharedPluginsPath, entry.name); - const stats = fs.statSync(entryPath); + let stats: fs.Stats; + try { + stats = fs.statSync(entryPath); + } catch (err) { + const code = (err as NodeJS.ErrnoException).code; + console.log( + warn( + `Skipping plugins/${entry.name}: unable to inspect shared plugin entry${code ? ` (${code})` : ''}` + ) + ); + continue; + } + items.set(entry.name, { name: entry.name, type: stats.isDirectory() ? 'directory' : 'file', diff --git a/tests/unit/shared-manager.test.ts b/tests/unit/shared-manager.test.ts index 063ae9a4..555953eb 100644 --- a/tests/unit/shared-manager.test.ts +++ b/tests/unit/shared-manager.test.ts @@ -307,6 +307,31 @@ describe('SharedManager', () => { }); describe('marketplace registry ownership', () => { + it('skips unstatable shared plugin entries during instance linking', () => { + const manager = new SharedManager(); + const instancePath = instanceDir('work'); + const sharedPluginsPath = path.join(claudeDir(), 'plugins'); + const logSpy = spyOn(console, 'log').mockImplementation(() => {}); + + fs.mkdirSync(instancePath, { recursive: true }); + fs.mkdirSync(sharedPluginsPath, { recursive: true }); + fs.symlinkSync( + path.join(sharedPluginsPath, 'missing-plugin'), + path.join(sharedPluginsPath, 'broken-plugin'), + 'dir' + ); + + expect(() => manager.linkSharedDirectories(instancePath)).not.toThrow(); + expect(fs.existsSync(path.join(instancePath, 'plugins', 'broken-plugin'))).toBe(false); + expect( + logSpy.mock.calls.some(([message]) => + String(message).includes( + 'Skipping plugins/broken-plugin: unable to inspect shared plugin entry' + ) + ) + ).toBe(true); + }); + it('writes global and instance registries with different authoritative install locations', () => { const globalRegistryPath = path.join(claudeDir(), 'plugins', 'known_marketplaces.json'); ensureMarketplacePayload(claudeDir());