mirror of
https://github.com/thedotmack/claude-mem.git
synced 2026-09-20 04:23:02 +08:00
d959572bde
* fix(setup): guard plugin deps on completeness, not node_modules existence `ensurePluginDependencies()` decided whether to run `bun install` by asking whether `node_modules/` existed. A tree that is merely present — but short of the declared closure — satisfied that check and permanently skipped repair on every subsequent Setup run. Two trigger paths, and the second needs no corruption at all: 1. An install interrupted mid-fetch (network timeout, OOM, registry 5xx) leaves `node_modules/` behind incomplete. 2. A tree that was complete *for the version that created it*. `zod` was added to plugin deps after some users had already installed; their node_modules has been incomplete ever since, and no upgrade heals it because the stale tree is gitignored and gets re-seeded into each new cache version. The worker then dies at boot on `Cannot find module 'zod/v3'` while memory search keeps working — `mcp-server.cjs` bundles zod (build-hooks.js:519 hard-fails if it ever externalizes it) while `worker-service.cjs` has 19 external zod requires. So the plugin looks alive while capture is dead. One reporter lost ~4 months of capture with no visible symptom. Guard on completeness instead: every key of `package.json` `dependencies` must resolve, with a `<dep>/package.json` fallback for bin-only packages like tree-sitter-cli (gh #2730), plus the zod subpaths the worker requires. This mirrors `verifyCriticalModules` (src/npx-cli/install/setup-runtime.ts:245), which already applies exactly this contract on the npx install path but was never reachable from the Setup hook. It cannot be imported here — this script is standalone and dependency-free — so the probe is inlined and the two are cross-referenced. Two consequences fall out: - The post-failure `rmSync` of node_modules is removed. It existed only because the existence guard would otherwise block retry forever; the completeness guard re-detects the gap on the next run, so deleting bought nothing while actively destroying a partial tree that still powers search. - A zero exit from `bun install` is no longer trusted. It can exit 0 with a failed integrity check, so the closure is re-probed afterwards and the diagnostic reports what actually resolves. The install diagnostic now names the unresolvable modules, which is what stops this failure mode from being silent. Fixes #3755. Refs #3604 (plan-16), #2730. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NT5K64VU4a7Kbc36oTVjyc * fix(setup): keep the completeness probe inside the plugin's own node_modules Greptile P1 on #3872, and it was right. `require.resolve(dep, { paths: [nodeModulesPath] })` reads as tree-scoped but is not. `paths` only seeds Node's lookup; resolution then walks every ancestor directory and always consults the global folders ($HOME/.node_modules, $PREFIX/lib/node). Plugin roots live at ~/.claude/plugins/cache/thedotmack/claude-mem/<version>/, so a copy of a dependency anywhere above them — or installed globally — answered for the plugin's own. Verified against the committed probe: a plugin whose node_modules is completely EMPTY, with a valid zod one directory up, exits 0 with no output and never runs the install. That is the #3755 bug reintroduced by the fix for it, and it would have shipped silently. Presence is now checked by statting `<node_modules>/<dep>/package.json` directly, which cannot escape the tree. This is also the signal scripts/check-postinstall-allowlist.js:75-78 already uses, and it handles scoped names (split into path segments) and bin-only packages like tree-sitter-cli (package.json present, no entry point — gh #2730) without the bare-name/fallback dance. zod's subpaths still need real resolution, since they are `exports` entries that a present directory does not guarantee. Those are now accepted only when the resolved file lands inside the plugin's own zod directory. Both sides are realpath'd before comparison: bun can materialise node_modules entries as links into a shared store, and Node returns the real path of what it resolved, so a literal comparison would report a healthy linked install as missing and loop the install forever. Containment uses path.relative rather than string prefixing so a sibling like zod-extra is not mistaken for being inside zod. Regression test added; it fails against the previous commit's probe. Note for follow-up: verifyCriticalModules (setup-runtime.ts:245) has the same ancestor/global escape. It is far less dangerous there — a post-install assertion that fails loud, not the gate deciding whether repair runs at all — but worth tightening. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NT5K64VU4a7Kbc36oTVjyc --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>