You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Plugin fails to load when host has libvips installed globally: sharp@0.34.5 postinstall picks source build, fails, leaves corrupt node_modules that traps wrapper recovery loop
Summary
episodic-memory@1.4.2 fails to load as a Claude Code plugin MCP server on hosts where libvips is installed globally (common on Arch Linux, many pkg-config-friendly distros, anything that bundles libvips for another package like GIMP/darktable/ImageMagick wrappers). The failure chain:
Wrapper sees missing deps, runs npm install.
sharp@0.34.5's postinstall (install/check.js) detects the host libvips and intentionally process.exit(1) to force a source build — even though the correct prebuilt @img/sharp-linux-x64@0.34.5 already exists as an optionalDependency and would work fine.
The source-build fallback (npm run build → node-gyp rebuild) fails immediately because sharp@0.34.5 doesn't declare node-addon-api as a runtime dependency. npm aborts the install.
The aborted install leaves npm's .<pkg>-<hash>/ atomic-rename temp directories in node_modules/ — some with no package.json.
The plugin stays in a stuck state across sessions until the corrupt node_modules is wiped by hand.
This is the same symptom class as #95 (partial node_modules → wrapper retry storm) and the same theme as #100 (native-dep postinstall broken on newer Node), but a distinct, separately filable root cause: sharp's useGlobalLibvips() heuristic on Linux hosts that happen to have libvips installed.
Environment
Plugin version: 1.4.2 (installed via superpowers-marketplace)
OS: Arch Linux, kernel 7.0.9-zen2-1-zen, x86_64
Node: v24.14.1
npm: 11.14.1
Host libvips: libvips 8.18.2 (installed via pacman as a dep of unrelated packages)
useGlobalLibvips() shells out to pkg-config --modversion vips-cpp and returns true if a sufficiently new libvips is present. On Arch with libvips 8.18.2 installed, it returns true, and check.js exits 1 by design.
That exit code is the trigger sharp uses to switch from "use the prebuilt platform-specific binary" to "build from source against the host libvips."
Step 2: Source build fails because sharp doesn't ship node-addon-api as a dep
sharp: Attempting to build from source via node-gyp
sharp: See https://sharp.pixelplumbing.com/install#building-from-source
sharp: Please add node-addon-api to your dependencies
No node-addon-api. The source-build path is non-functional out of the box. (This is arguably a sharp upstream bug, but it interacts badly with the next two steps and the fix lives just as well in episodic-memory.)
Several of these dot-prefixed dirs are missing their package.json.
Step 4: Wrapper retry hits Invalid Version: in arborist
Next launch, findMissingDeps (#95) correctly detects deps are missing and runs npm install again. But arborist's canDedupe walks node_modules/.package-lock.json, encounters one of the empty temp dirs, and crashes:
npm error Invalid Version:
npm error
verbose stack TypeError: Invalid Version:
verbose stack at new SemVer (/usr/lib/node_modules/semver/classes/semver.js:40:13)
verbose stack at compare (/usr/lib/node_modules/semver/functions/compare.js:5:32)
verbose stack at Object.eq (/usr/lib/node_modules/semver/functions/eq.js:4:29)
verbose stack at Node.canDedupe (/usr/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/node.js:1137:32)
verbose stack at PlaceDep.pruneDedupable (/usr/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/place-dep.js:426:14)
verbose stack at new PlaceDep (/usr/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/place-dep.js:278:14)
verbose stack at #buildDepStep (...)
From the user's perspective the MCP server "just fails to connect" with no obvious clue that sharp is the trigger. Each subsequent session sees the same state and retries the same failing install.
Reproduction
On any Linux host with libvips installed system-wide (pacman -S libvips on Arch; equivalent on other distros):
@img/sharp-linux-x64@0.34.5 is already declared in sharp's optionalDependencies and downloads fine. There is no reason this user's platform should ever fall through to the source-build path — the prebuilt binary is the documented happy path for linux-x64-glibc.
Suggested direction (not prescriptive)
Pick whichever of these fits the project's taste:
.npmrc in the plugin root with sharp_ignore_global_libvips=true. One line, persists across npm calls including the wrapper's recovery install, completely platform-neutral. Documented option from sharp itself: https://sharp.pixelplumbing.com/install#prebuilt-binaries. Lowest blast radius.
Set SHARP_IGNORE_GLOBAL_LIBVIPS=1 in the env of the npm install call in mcp-server-wrapper.js. Same effect, scoped to the wrapper's recovery path. Doesn't affect anyone running npm install manually in the plugin dir.
Add a one-time corruption cleanup before npm install in the wrapper. Walk node_modules/ for ^\.[a-zA-Z0-9_-]+-[A-Za-z0-9]{8}$ directories that lack a package.json and delete them before retry. Defensive, helps recover from any aborted install, not just sharp-triggered ones.
(1) and (4) together would make this fully self-healing on hosts that hit it for the first time and on hosts where it has already left corruption from a previous launch.
#95's wrapper fix correctly probes findMissingDeps instead of trusting existsSync(node_modules). That probe now successfully detects the broken state caused by this issue. The follow-up npm install still fails — first because sharp re-triggers the same source-build path, second because the leftover temp dirs from the prior failed install poison arborist before sharp even gets called.
#95 closed the "missed broken state" gap. This issue is one specific way to create that broken state, with a separate fix that doesn't overlap.
#100 is about better-sqlite3 postinstall being a silent no-op on Node 25 (so the install reports healthy but the binding never builds). This issue is the opposite shape: sharp's postinstall reports failure loudly (exit 1), npm aborts, no install completes at all. Different dep, different Node version, different failure mode — but both could share a single broader fix in the wrapper's recovery path (suggestion 3 above).
Plugin fails to load when host has
libvipsinstalled globally:sharp@0.34.5postinstall picks source build, fails, leaves corruptnode_modulesthat traps wrapper recovery loopSummary
episodic-memory@1.4.2fails to load as a Claude Code plugin MCP server on hosts wherelibvipsis installed globally (common on Arch Linux, manypkg-config-friendly distros, anything that bundles libvips for another package like GIMP/darktable/ImageMagick wrappers). The failure chain:npm install.sharp@0.34.5's postinstall (install/check.js) detects the host libvips and intentionallyprocess.exit(1)to force a source build — even though the correct prebuilt@img/sharp-linux-x64@0.34.5already exists as anoptionalDependencyand would work fine.npm run build→node-gyp rebuild) fails immediately becausesharp@0.34.5doesn't declarenode-addon-apias a runtime dependency. npm aborts the install..<pkg>-<hash>/atomic-rename temp directories innode_modules/— some with nopackage.json.findMissingDeps(added in Plugin fails to load on Windows: two upstream dep-resolution bugs slip past mcp-server-wrapper.js #95) detects the missing deps and retriesnpm install. This timearborist'scanDedupehits one of the empty temp dirs and crashes withTypeError: Invalid Version:(empty string fed tonew SemVer()). Install fails with a different, more confusing error.node_modulesis wiped by hand.This is the same symptom class as #95 (partial
node_modules→ wrapper retry storm) and the same theme as #100 (native-dep postinstall broken on newer Node), but a distinct, separately filable root cause: sharp'suseGlobalLibvips()heuristic on Linux hosts that happen to have libvips installed.Environment
1.4.2(installed viasuperpowers-marketplace)v24.14.111.14.1libvips 8.18.2(installed via pacman as a dep of unrelated packages)~/.claude/plugins/cache/superpowers-marketplace/episodic-memory/1.4.2/Step 1: sharp's
install/check.jsdeliberately fails when global libvips is presentnode_modules/sharp/install/check.js:useGlobalLibvips()shells out topkg-config --modversion vips-cppand returns true if a sufficiently new libvips is present. On Arch with libvips 8.18.2 installed, it returns true, and check.js exits 1 by design.That exit code is the trigger sharp uses to switch from "use the prebuilt platform-specific binary" to "build from source against the host libvips."
Step 2: Source build fails because sharp doesn't ship
node-addon-apias a depsharp@0.34.5'sdependencies:{ "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }No
node-addon-api. The source-build path is non-functional out of the box. (This is arguably a sharp upstream bug, but it interacts badly with the next two steps and the fix lives just as well inepisodic-memory.)Step 3: Aborted install leaves corrupt
node_modulesAfter npm aborts on sharp's failed build,
node_modules/contains npm's intermediate atomic-rename temp dirs that never got moved into place:Several of these dot-prefixed dirs are missing their
package.json.Step 4: Wrapper retry hits
Invalid Version:in arboristNext launch,
findMissingDeps(#95) correctly detects deps are missing and runsnpm installagain. But arborist'scanDedupewalksnode_modules/.package-lock.json, encounters one of the empty temp dirs, and crashes:From the user's perspective the MCP server "just fails to connect" with no obvious clue that sharp is the trigger. Each subsequent session sees the same state and retries the same failing install.
Reproduction
On any Linux host with libvips installed system-wide (
pacman -S libvipson Arch; equivalent on other distros):Verification of the fix
Two ways to confirm the prebuilt path works for this user's platform:
@img/sharp-linux-x64@0.34.5is already declared in sharp'soptionalDependenciesand downloads fine. There is no reason this user's platform should ever fall through to the source-build path — the prebuilt binary is the documented happy path forlinux-x64-glibc.Suggested direction (not prescriptive)
Pick whichever of these fits the project's taste:
.npmrcin the plugin root withsharp_ignore_global_libvips=true. One line, persists across npm calls including the wrapper's recovery install, completely platform-neutral. Documented option from sharp itself: https://sharp.pixelplumbing.com/install#prebuilt-binaries. Lowest blast radius.Set
SHARP_IGNORE_GLOBAL_LIBVIPS=1in the env of thenpm installcall inmcp-server-wrapper.js. Same effect, scoped to the wrapper's recovery path. Doesn't affect anyone runningnpm installmanually in the plugin dir.Pass
--ignore-scriptsto the wrapper's recoverynpm install. Broader — also skipsbetter-sqlite3's postinstall rebuild (relevant to postinstallnpm rebuild better-sqlite3is a silent no-op on Node 25 — binding never builds, recovery hint repeats the dead command #100). Combined with postinstallnpm rebuild better-sqlite3is a silent no-op on Node 25 — binding never builds, recovery hint repeats the dead command #100's instantiate-to-verify check, this would make the recovery path resilient to native-dep postinstall bugs in general, not just sharp.Add a one-time corruption cleanup before
npm installin the wrapper. Walknode_modules/for^\.[a-zA-Z0-9_-]+-[A-Za-z0-9]{8}$directories that lack apackage.jsonand delete them before retry. Defensive, helps recover from any aborted install, not just sharp-triggered ones.(1) and (4) together would make this fully self-healing on hosts that hit it for the first time and on hosts where it has already left corruption from a previous launch.
Why this is distinct from #95
#95's wrapper fix correctly probes
findMissingDepsinstead of trustingexistsSync(node_modules). That probe now successfully detects the broken state caused by this issue. The follow-upnpm installstill fails — first because sharp re-triggers the same source-build path, second because the leftover temp dirs from the prior failed install poison arborist before sharp even gets called.#95 closed the "missed broken state" gap. This issue is one specific way to create that broken state, with a separate fix that doesn't overlap.
Why this is distinct from #100
#100 is about
better-sqlite3postinstall being a silent no-op on Node 25 (so the install reports healthy but the binding never builds). This issue is the opposite shape: sharp's postinstall reports failure loudly (exit 1), npm aborts, no install completes at all. Different dep, different Node version, different failure mode — but both could share a single broader fix in the wrapper's recovery path (suggestion 3 above).