Symptom (live user report, 2026-07-04)
nodeup check reported LTS: v22.10.0 (released 2025-02-04) / Current: v24.0.0 (released 2025-04-01) while the live nodejs.org index has LTS v24.18.0 / Current v26.4.0 — and the user's own fnm had 26.4.0 installed. Reproduced with a fresh build of main (c5b4301).
Root cause
~/Library/Caches/nodeup/node-dist-index.json contained a 115-byte copy of dist_test.go's validManifestBody fixture (the exact two entries above), with a fresh .meta expiry. A test run from before the withEmptyCache isolation helper landed (#82 fix-wave) executed FetchManifestCtx against an httptest server and saveToCache wrote the fixture to the real user cache. nodeup check then faithfully served the fixture until the 24h TTL lapsed. Current tests were verified clean — running go test ./internal/node/ today does not touch the real cache — so this specific incident self-heals and the immediate remedy is just deleting the two files.
The bugs worth fixing
-
Cache location contradicts the platform layer and the docs. internal/node/dist.go cachePath() builds os.UserCacheDir()/nodeup (macOS: ~/Library/Caches/nodeup). But platform.CacheDir() — and CLAUDE.md's on-disk layout section — say the cache lives at <DataDir>/cache (macOS: ~/Library/Application Support/nodeup/cache). Both directories exist on disk; the platform one sits empty while the real cache accumulates elsewhere. Pick one (presumably platform.CacheDir(), since it's the documented invariant and the platform package exists precisely so paths aren't derived ad-hoc) and migrate.
-
Nothing stops a future test from re-polluting. withEmptyCache(t) is opt-in per test. One new FetchManifestCtx-calling test that forgets it silently writes fixtures into the developer's real cache again. Consider a package-level guard — e.g. TestMain in internal/node that sets the cache-redirect env vars once for the whole package, or making defaultCachePaths a swappable seam that tests must set.
Suggested fix shape
cachePath() → delegate to platform.CacheDir(); keep the filenames (node-dist-index.json + .meta).
- One-time migration is unnecessary (worst case: one cold fetch after upgrade); optionally delete the legacy
os.UserCacheDir()/nodeup dir when found.
- Add
TestMain cache isolation to internal/node so pollution is structurally impossible.
- Tests: assert
cachePath() lives under platform.DataDir().
Symptom (live user report, 2026-07-04)
nodeup checkreportedLTS: v22.10.0 (released 2025-02-04)/Current: v24.0.0 (released 2025-04-01)while the live nodejs.org index has LTS v24.18.0 / Current v26.4.0 — and the user's own fnm had 26.4.0 installed. Reproduced with a fresh build ofmain(c5b4301).Root cause
~/Library/Caches/nodeup/node-dist-index.jsoncontained a 115-byte copy ofdist_test.go'svalidManifestBodyfixture (the exact two entries above), with a fresh.metaexpiry. A test run from before thewithEmptyCacheisolation helper landed (#82 fix-wave) executedFetchManifestCtxagainst an httptest server andsaveToCachewrote the fixture to the real user cache.nodeup checkthen faithfully served the fixture until the 24h TTL lapsed. Current tests were verified clean — runninggo test ./internal/node/today does not touch the real cache — so this specific incident self-heals and the immediate remedy is just deleting the two files.The bugs worth fixing
Cache location contradicts the platform layer and the docs.
internal/node/dist.gocachePath()buildsos.UserCacheDir()/nodeup(macOS:~/Library/Caches/nodeup). Butplatform.CacheDir()— and CLAUDE.md's on-disk layout section — say the cache lives at<DataDir>/cache(macOS:~/Library/Application Support/nodeup/cache). Both directories exist on disk; the platform one sits empty while the real cache accumulates elsewhere. Pick one (presumablyplatform.CacheDir(), since it's the documented invariant and the platform package exists precisely so paths aren't derived ad-hoc) and migrate.Nothing stops a future test from re-polluting.
withEmptyCache(t)is opt-in per test. One newFetchManifestCtx-calling test that forgets it silently writes fixtures into the developer's real cache again. Consider a package-level guard — e.g.TestMainininternal/nodethat sets the cache-redirect env vars once for the whole package, or makingdefaultCachePathsa swappable seam that tests must set.Suggested fix shape
cachePath()→ delegate toplatform.CacheDir(); keep the filenames (node-dist-index.json+.meta).os.UserCacheDir()/nodeupdir when found.TestMaincache isolation tointernal/nodeso pollution is structurally impossible.cachePath()lives underplatform.DataDir().