Skip to content

fix(doctor): stop asserting a version claim it cannot back up, and name the install it is not - #285

Merged
kevintseng merged 3 commits into
mainfrom
fix/doctor-install-skew
Sep 3, 2026
Merged

fix(doctor): stop asserting a version claim it cannot back up, and name the install it is not#285
kevintseng merged 3 commits into
mainfrom
fix/doctor-install-skew

Conversation

@kevintseng

Copy link
Copy Markdown
Contributor

Summary

Dogfooding the published 4.8.3 found memesh doctor telling the user something false, and staying silent about something worse.

It asserted a version claim it cannot back up. The npm-global install printed [PASS] Update status — Version 4.8.2 is current. at a moment when npm had already published 4.8.3. Doctor never makes a live registry call (deliberately); it reads a cached update-check, and that cache was ~23h44m old — inside STALE_AFTER_MS, so still classified cached rather than stale, so the claim printed unqualified. True when the cache was written, false when doctor read it, and nothing in the sentence let a reader tell the difference. The row now reads "As of the last check (23 hours ago), 4.8.2 was the latest version." — same information, falsifiable.

It could not see that two installs were running different code. The Claude Code plugin was on 4.8.3 and the shell CLI on PATH on 4.8.2 — an agent and the human typing memesh in a terminal running different code against one database. inspectShellCli already resolved the other install's path and reported that the two coexist; it never read that install's version. It now does, via readVersionFromInstalledBinary: follow npm's bin symlink with realpathSync, then walk up a bounded eight levels to the nearest package.json and trust it only when its name matches — a longer or unchecked walk would report an unrelated package's version as memesh's own.

And the stale copy can never fix itself. spawnAutoUpdate derives its channel from whichever copy's hook is executing; hooks are wired to the plugin copy, so the channel is always plugin-marketplace and ~/.memesh/auto-update.log holds 45 consecutive SKIPPED lines — correct for the copy that logged it, silently incomplete for the user, whose npm-global copy is the one that needs a manual memesh update. The npm-global plugin-cache row now says so, and mentions the cached-copy count once it exceeds two.

Type of change

  • Bug fix (fix)

Docs synced

  • CHANGELOG.md [Unreleased]### Fixed
  • dist/core/doctor.* regenerated (tracked build output)
  • No doctor check inventory or count exists in the docs — check-doc-claims confirms there is nothing to update

Verification

  • node scripts/run-tests-isolated.mjs tests/core/doctor.test.ts → exit 0, 159 passed (149 pre-existing + 10 new)
  • Mutation (force shellIsBehind null, dist/ rebuilt) → exit 1, 1 failed / 158 passed; restored → 159/159
  • npm run lint 0, npm run typecheck 0, check-doc-claims 0, npm run verify:release 0
  • Evidence Gates: eg check PASS at base 2d4c0f04doctor-install-skew CODE_COMPLETE with a known_bad_mutation failure_path
  • On this machine, isolated MEMESH_DIR, the new row fires correctly:
[WARN] Shell CLI on PATH
  `memesh` resolves to …/bin/memesh (separate from this install at …), and it is
  running 4.8.2 — behind this install's 4.8.3. Both share the same DB, but an agent
  using this install and a human typing `memesh` in a terminal are running different code.
  Fix: Run `npm install -g @pcircle/memesh@latest` …

Test plan

CI on this head. After merge, memesh doctor on any machine with both a plugin and a global install will name the skew instead of reporting PASS.

…me the install it is not

Dogfooding the published 4.8.3 found `memesh doctor` printing
`[PASS] Update status — Version 4.8.2 is current.` on the npm-global install at
the moment npm had already published 4.8.3. Nothing in that sentence was
checkable: doctor never makes a live registry call (deliberately), it reads a
cached update-check, and the cache was ~23h44m old — inside STALE_AFTER_MS, so
still classified 'cached' rather than 'stale', so the claim printed
unqualified. It was true when the cache was written and false when doctor read
it, and a reader could not tell the difference. The row now says
"As of the last check (23 hours ago), 4.8.2 was the latest version." — the same
information, but falsifiable.

The same machine had a second, larger problem doctor was structurally unable to
see: the Claude Code plugin on 4.8.3 and the shell CLI on PATH on 4.8.2, so an
agent and the human typing `memesh` in a terminal were running different code
against one database. `inspectShellCli` already resolved the other install's
path and said the two coexist; it never read that install's version.
`readVersionFromInstalledBinary` now follows npm's bin symlink with
`realpathSync` and walks up a bounded eight levels to the nearest package.json,
trusting it only when its `name` matches — a longer walk risks reporting an
unrelated package's version as memesh's own. When the shell CLI is behind, the
row is a WARN naming both versions with channel-correct advice.

Third: on a machine with both installs, the auto-updater can never fix the
stale one. `spawnAutoUpdate` derives its channel from whichever copy's hook is
executing, hooks are wired to the plugin copy, so the channel is always
plugin-marketplace and the log is 45 consecutive SKIPPED lines — correct for
the copy that logged it, silently incomplete for the user, whose npm-global
copy is the one needing a manual `memesh update`. The npm-global plugin-cache
row now says so, and mentions the cached-copy count once it exceeds two (size
is deliberately not computed — `du` over a node_modules-sized tree on every
doctor run is not worth the number).

Refs #240-class (a check that states something it did not verify)

[Verified-By: node scripts/run-tests-isolated.mjs tests/core/doctor.test.ts -> exit=0, "Tests 159 passed (159)"; mutation (shellIsBehind forced false, dist rebuilt) -> exit=1, "Tests 1 failed | 158 passed (159)"; restored -> exit=0, "Tests 159 passed (159)"; real machine, isolated MEMESH_DIR: doctor prints "[WARN] Shell CLI on PATH … it is running 4.8.2 — behind this install's 4.8.3"]
…ng it could not tell

`detectPluginHost(packageRoot) ?? 'claude-code'` collapsed a legitimate `null`
— "this path is not under any plugin cache" — into a confident answer. A Codex
user whose cache location this process cannot resolve was told to run `memesh
upgrade-plugin`, which does nothing for them, and nothing in the message marked
it as a guess.

When the host is detectable the advice still names only that host's command;
when it is not, it names both. Same defect and same fix as the session-start
banner's `pluginHostOf`, found the same way.

The test that should have caught this was pinning the bug instead. Its fixture
builds `packageRoot` under `os.tmpdir()`, which is not under any plugin cache,
so it exercised precisely the undetectable-host branch — and asserted the
Claude Code command was correct there. It now asserts both commands appear, and
a second test builds a `packageRoot` under `.claude/plugins/cache/` and asserts
the Codex command does NOT appear, so "always print both" cannot pass either.

[Verified-By: node scripts/run-tests-isolated.mjs > /tmp/f285b.log 2>&1; echo
exit=$? -> exit=0; "Test Files  229 passed | 1 skipped (230)", "Tests  3286
passed | 11 skipped (3297)", no Errors line]
[Verified-By: npm run verify:release > /tmp/v285b.log 2>&1; echo exit=$? ->
exit=0, ending "consumer install has no high-or-worse advisories"]
[Verified-By: break-test, mutate -> run -> restore with byte-identical restore
asserted by reading the file back: put `?? 'claude-code'` back -> exit=1
(only the named assertion failed, 159/160 still ran), restored -> exit=0]
[Verified-By: MEMESH_DIR=<throwaway> eg check -> exit=0, "PASS ... rev=0059b110
... doctor-install-skew: source_library required=CODE_COMPLETE
observed=CODE_COMPLETE". changed=0 because the contract is frozen at this HEAD,
so eg's uncovered-changed-paths check is vacuous here, as in every ledger in
this batch]
@kevintseng
kevintseng force-pushed the fix/doctor-install-skew branch from eb005c7 to 380b548 Compare September 3, 2026 03:50
The `?? 'claude-code'` fallback it described only existed in this PR's
own first commit and was replaced before the PR was opened; main never
carried it. Left in, it would read as a real regression fixed by this
release.
@kevintseng
kevintseng merged commit 1ead5f6 into main Sep 3, 2026
23 of 24 checks passed
kevintseng added a commit that referenced this pull request Sep 3, 2026
The rebase onto origin/main (post-#285 merge)'s own conflict resolution
took the wrong side for these two map files; a full rebuild produces the
correct byte content.
kevintseng added a commit that referenced this pull request Sep 3, 2026
The rebase onto origin/main (post-#285 merge)'s own conflict resolution
took the wrong side for these two map files; a full rebuild produces the
correct byte content.
kevintseng added a commit that referenced this pull request Sep 4, 2026
The rebase onto origin/main (post-#285 merge)'s own conflict resolution
took the wrong side for these two map files; a full rebuild produces the
correct byte content.
kevintseng added a commit that referenced this pull request Sep 4, 2026
The rebase onto origin/main (post-#285 merge)'s own conflict resolution
took the wrong side for these two map files; a full rebuild produces the
correct byte content.
@kevintseng
kevintseng deleted the fix/doctor-install-skew branch September 5, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant