v4.2.5 feat(install): plugin-marketplace channel + one-line upgrade flow#46
Conversation
….2.5) Claude Code's plugin marketplace pins versions at install time and does not auto-update — every v4.2.3 user was stuck until they manually reinstalled. This adds the missing pieces so memesh tells users a new version exists and gives them a single command to apply it. - src/core/install-channel.ts: new `plugin-marketplace` channel that matches `~/.claude/plugins/cache/<marketplace>/<plugin>/<version>` paths. The check runs before the `.git`/npm-global checks because the plugin cache is itself a git clone. Channel routes through its own `InstallChannelSupport` entry instead of falling into `unknown` (where guidance was generic "upgrade via your install method"). - scripts/upgrade-plugin.sh: idempotent one-line upgrade. Fast-forwards the marketplace cache, rsyncs to a new version directory under the plugin cache, runs `npm install --omit=dev`, patches installed_plugins.json. Pre-flights all required tools and refuses to destructively reset a local-committed marketplace cache. - scripts/hooks/session-start.js: new `buildUpdateAvailableBanner()`. Fires only when the installed version is NOT deprecated but a newer release exists on npm. One info line with the channel-tailored upgrade command. Throttled to once per 24h per installed version. Defers to the existing deprecation banner when both apply. - README, CHANGELOG, ARCHITECTURE, API_REFERENCE: documented the three upgrade paths and the new channel. Version bumped 4.2.4 -> 4.2.5 across all 7 anchor files; dist/skills-manifest.json regenerated. - tests/install-channel.test.ts: covers the new channel detection (including the priority-over-.git case) and the new `InstallChannelSupport` entry. Validation: - npm test --run: 1035 passed (was 1031 baseline) - npm run build: clean - memesh doctor: Overall PASS_WITH_CONCERNS (only WARN is unrelated hook-activity on a cold install). All security / version / manifest checks PASS. - npm pack --dry-run: confirms scripts/upgrade-plugin.sh ships in the tarball.
Multi-Model SynthesisBoth Claude and Codex reviews above are independent. The most Reviewer responsibility: read both, surface non-overlapping |
1. upgrade-plugin.sh: stop interpolating bash vars directly into JS string literals in sections 2 + CURRENT_VERSION read. The previous form would have broken if a path contained `'` or `\n`; the rest of the script (section 5) already used the env-passing pattern, so this is now consistent. 2. session-start.js: replace lexicographic version compare with componentwise numeric `isStrictlyOlder()`. The previous form would have silently suppressed the update banner once memesh hit a two-digit patch (`'4.2.10' < '4.2.9'` evaluates true). 8/8 cases verified, including prerelease tags. Also regenerated dist/skills-manifest.json. No version bump — this is polish on the same v4.2.5 PR before merge. Validation: - npm test --run: 1035/1035 pass - npx tsc --noEmit: clean - bash -n + dry-run upgrade-plugin.sh: clean - memesh doctor: Overall: PASS
Multi-Model SynthesisBoth Claude and Codex reviews above are independent. The most Reviewer responsibility: read both, surface non-overlapping |
Root cause uncovered by /investigate: Claude Code's `/plugin install`
runs `npm install --ignore-scripts` (security default), which skips
better-sqlite3's `install` script (fetches/builds the native .node
binding) AND skips memesh's own `postinstall-rebuild.mjs` safety net.
Result: better-sqlite3 JS files are present but the native binding is
missing. Every Stop hook then reaches tryRequireBetterSqlite(), gets
null, throws "skip-session-capture: better-sqlite3 unavailable", and
exits 0. No error visible to Claude Code, no entity written. 22 Stop
events fired in the investigation session — zero entities captured.
This is the silent-dropout that has been hitting plugin-marketplace
users on Node ABI versions not covered by better-sqlite3 prebuilts
(Node 24 / ABI v137 currently uncovered) since memesh was published
as a plugin. Memesh's "self-improving memory" loop was effectively off
for every affected user.
Two-layer fix:
1. scripts/hooks/_shared.js — when the binding probe fails,
tryRequireBetterSqlite() now spawns a detached `npm rebuild
better-sqlite3` in the package root. The current hook still exits
silently (caller already silent-skips), but the *next* session
captures normally. Throttled to one attempt per hour via an
O_EXCL marker (~/.memesh/last-rebuild-attempt.lock) so a
crash-loop can't drive a rebuild storm. Gated on !_inTestEnv()
so CI doesn't spawn npm. Stderr trace surfaces both the fix
attempt AND the manual rebuild command, so the user has a path
forward even if the async rebuild fails.
2. src/core/doctor.ts — new check `Native SQLite binding`. Probes
the binding by instantiating `new Database(':memory:')` (a bare
`require()` is not sufficient — JS wrapper succeeds even when
the .node is missing). FAIL surfaces the exact rebuild command.
Catches the silent-dropout class of bug that previously hid
behind the existing "Hook activity (last 24h)" WARN, which used
a 24h grace period that swallowed fresh installs.
ESM-safe: uses `createRequire(pathToFileURL(...).href)` because
the doctor module compiles to ESM; bare `require('module')`
would throw "require is not defined".
3. tests/core/doctor.test.ts — 3 new tests cover:
- missing binding → FAIL with `npm rebuild` fix message
- node_modules/better-sqlite3 entirely absent → FAIL with
`npm install --omit=dev` fix message
- probe succeeds → PASS
Validation:
- npm test --run: 1038/1038 (was 1035, +3 new)
- npm run build: clean
- memesh doctor: Overall: PASS (new check passes on healthy install)
- Manually verified: 4.2.3 install path reproduces the exact failure
signature ("Could not locate the bindings file"); 4.2.4 install
with npm install passes the probe.
Multi-Model SynthesisBoth Claude and Codex reviews above are independent. The most Reviewer responsibility: read both, surface non-overlapping |
…w doctor text
User-reported UX bug: the doctor banner on the dashboard showed an
alarmist title ("Heads up — memesh setup needs attention") above a
self-contradicting body ("Install method: Installation method
detection — No action needed"). Three intersecting causes:
1. The banner fired for every `PASS_WITH_CONCERNS` doctor result,
including WARN checks whose `fix` field was a generic
"No action needed" placeholder — meaning the user had no path
forward, just an alarming yellow banner above unactionable text.
2. The banner preferred a generic `doctor.<id>.summary` i18n override
over the raw doctor `summary`/`fix`. For PASS state that's a
friendlier label, but for FAIL/WARN it destroyed the actual
diagnostic detail — a "Native binding missing" FAIL would render
as the generic PASS-state label "Native binding detected", and
the actual rebuild command was hidden.
3. The `doctor.install-channel.fix: 'No action needed'` i18n
override was flat-out wrong — `install-channel` is WARN when
the install method can't be detected; that's actionable, not
"no action needed".
Three fixes:
1. dashboard/src/components/DoctorBanner.tsx — new `isActionable()`
filter: FAIL always counts, WARN counts only when `fix` is
present AND isn't a "no action needed" placeholder. Banner is
hidden when zero actionable concerns remain. The CLI / API
still surfaces every doctor finding; the dashboard banner just
stops popping for ones the user can't act on.
2. DoctorBanner uses raw `c.summary` / `c.fix` directly. The i18n
override path is removed — for the banner's purpose (showing
FAIL/WARN), the raw doctor text is always more diagnostic.
3. Banner tiers severity:
- FAIL → strong title "memesh setup is incomplete" + Get help
button (file GitHub issue)
- WARN-only → softer title "memesh has setup notes" (new
`doctorBanner.warnTitleSoft` i18n key, translated across all
11 locales), no GitHub-issue CTA — the in-body fix command
is the actionable path.
4. dashboard/src/lib/i18n.ts — deleted the misleading
`doctor.install-channel.summary` and `doctor.install-channel.fix`
overrides across all 11 locales (English + 10 translations).
The check's real channel-aware fix field now reaches the user
verbatim.
Validation:
- Built dashboard + main; smoke tests pass
- Manually verified live: doctor → Overall PASS on v4.2.5 install,
banner correctly hidden
- 2 pre-existing failures on main (tests/transports/http.test.ts
recall-empty, tests/tools.test.ts auto-archive) reproduce without
this change — not caused by this PR
Multi-Model SynthesisBoth Claude and Codex reviews above are independent. The most Reviewer responsibility: read both, surface non-overlapping |
…prod seam removal
Three findings from a fresh /review pass on the cumulative v4.2.5 diff:
1. [P2] scripts/hooks/_shared.js — detached `npm rebuild` had no 'error'
listener. If `npm` is missing from PATH (rare but real on containers /
stripped-down CI), Node emits an async 'error' event that becomes an
uncaught exception. The outer synchronous try/catch can't catch it,
so a hook crash here would turn the silent-dropout bug into a
broken-hook story. Added `child.on('error', () => {})` to match the
"self-heal is best-effort" contract — the binding probe already
leaves a stderr breadcrumb with the manual fix.
2. [P3] scripts/hooks/_shared.js — inline `require('fs').statSync` /
`require('fs').unlinkSync` reached around the module's top-level
named imports. Added `statSync, unlinkSync` to the import line so
the file stays consistent.
3. [P3] src/core/doctor.ts — defaultNativeBindingProbe had a
`process.env.VITEST === 'true'` test seam. Too permissive: a user
who happened to have VITEST exported in their shell (e.g. shared
across projects) would silently see PASS on a broken binding —
masking the exact failure mode this whole check exists to catch.
Removed the production seam entirely. Tests inject
`nativeBindingProbeImpl` explicitly via runDoctor options — updated
the 2 PASS-path doctor tests to pass `() => ({ ok: true })`. Now
production code paths always exercise the real probe.
Validation:
- npm test --run: 1036/1038 (same 2 pre-existing failures on main —
tests/transports/http.test.ts recall-empty, tests/tools.test.ts
auto-archive — unrelated to this change)
- npx vitest run tests/core/doctor.test.ts: 30/30
- memesh doctor (via HTTP): Overall PASS_WITH_CONCERNS, native-binding PASS
- npm run build: clean (smoke 6/6)
Multi-Model SynthesisBoth Claude and Codex reviews above are independent. The most Reviewer responsibility: read both, surface non-overlapping |
…sis to source Eight CodeQL findings on PR #46: [HIGH security] js/file-system-race at scripts/hooks/_shared.js:424 Root cause: the self-heal block's stale-marker cleanup was a 3-step dance (statSync → unlinkSync → openSync('wx')) where a peer hook could insert between any two steps. Worst-case outcome was duplicate `npm rebuild` spawns or one peer's fresh lock being stomped by another peer's stale-cleanup. Fix: drop the stale-cleanup path entirely. Use a single atomic O_EXCL create. Once the marker exists, every future hook bails. If a rebuild fails, the user clears the marker manually — the stderr breadcrumb now logs the exact `rm <markerPath> && npm rebuild better-sqlite3` command. No retry-loop here would either re-introduce the race or burn CPU on a broken npm config. [7 dashboard/dist/index.html findings — not actionable in source] Three `js/property-access-on-non-object` errors, three `js/automatic-semicolon-insertion` notes, one `js/trivial-conditional` warning. All on the single-file minified Preact bundle. The patterns are minification + bundler artifacts (Vite runtime helpers with defensive null-fallback, constant-folded conditionals, semicolon-strip), not real bugs in source. Fix: add an advanced-setup CodeQL config that includes source paths (src/, scripts/, dashboard/src/, tests/, hooks/) and excludes built outputs (dist/, dashboard/dist/, minified bundles). This is the GitHub-recommended pattern for repos that ship pre-built artifacts. Source for each bundled module is already analyzed via the `paths` include — the analysis surface is unchanged, only the noise is removed. Validation: - npm test --run: 1036/1038 (same 2 pre-existing failures unrelated) - npm run build: clean - npx tsc --noEmit: clean - typecheck + targeted hook/doctor/install-channel tests: 59/59
Multi-Model SynthesisBoth Claude and Codex reviews above are independent. The most Reviewer responsibility: read both, surface non-overlapping |
Leftover from the prior commit's TOCTOU refactor — when I dropped the stale-marker cleanup branch, the imports and TTL constant became dead. ESLint flagged 3 warnings at --max-warnings 0 (hard CI gate). Net behavior: unchanged. The marker is one-shot atomic O_EXCL — TTL was the variable that gated stale-cleanup, which no longer exists.
Multi-Model SynthesisBoth Claude and Codex reviews above are independent. The most Reviewer responsibility: read both, surface non-overlapping |
Summary
Claude Code's plugin marketplace pins versions at install time and does not auto-update. Every v4.2.3 user was stuck on the broken
npx -y -p @pcircle/memesh memesh-mcplauncher until they manually reinstalled. v4.2.4 fixed the launcher; this PR adds the missing UX so any future stale-version situation surfaces a banner + a one-command fix.Highlights
plugin-marketplaceinstall channel (src/core/install-channel.ts) detects~/.claude/plugins/cache/<marketplace>/<plugin>/<version>paths and routes through its ownInstallChannelSupportentry. Previously these were classified asunknownand got generic guidance.scripts/upgrade-plugin.sh— idempotent one-line upgrade. Fast-forwards the marketplace cache, rsyncs to a new version directory, runsnpm install --omit=dev, patchesinstalled_plugins.json.scripts/hooks/session-start.js— newbuildUpdateAvailableBanner(). Fires when the installed version is not deprecated but a newer release exists on npm. Single info line with the channel-tailored upgrade command. 24h throttle per installed version. Defers to the existing deprecation banner when both apply.Docs synced
memesh doctorreports PASS for README locale parityProject Architecture (v4.2.5)header + install-channel description bumped locally)Test plan
npm test -- --runpasses: 1035 / 1035 (was 1031 baseline; +4 new — install-channel detection + support entry)npm run buildpasses (smoke test: 6/6)node dist/transports/cli/cli.js doctor→ Overall: PASS_WITH_CONCERNS (only WARN is unrelatedHook activity (last 24h)— db has 0 entities locally)npm pack --dry-runconfirmsscripts/upgrade-plugin.shships in the tarball (7.0kB, total 233 files)bash -n scripts/upgrade-plugin.sh— syntax cleangit fetch origin && git merge --ff-onlyagainst marketplace cache, confirmed rsync + npm install + JSON patch reproduce the working v4.2.4 install state from a v4.2.3 baseline