Skip to content

feat(sync): prune npx cache envs serving outdated ruflo-family code - #29

Merged
pacphi merged 1 commit into
mainfrom
feat/sync-prunes-stale-npx-envs
Jul 17, 2026
Merged

pacphi merged 1 commit into
mainfrom
feat/sync-prunes-stale-npx-envs

Conversation

@pacphi

@pacphi pacphi commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Why

npx envs (<npm-cache>/_npx/<hash>/) are snapshots keyed by requested spec: once npx @claude-flow/cli caches a version, --prefer-offline serves that copy forever — upgrading the global install never touches it. The statusline/hook npx fallbacks execute these verbatim, which is how a machine running a fixed ruflo 3.32.2 kept rendering the fabricated CVE counter from a cached 3.28.0 (#28). On the machine that surfaced this, six such envs held ~6.4 GB of retired code (ruflo 3.10 / 3.21 / 3.25, @claude-flow/cli 3.28 ×2, agentic-qe 3.11.5 — the init-clobbering era).

This automates what #28 remediated by hand: a new npx status row and a matching sync heal.

The prune rule — conservative by construction

An env is removed only when all of the following hold; a miss fails safe as "not pruned", never as a wrong prune:

  • every package the env is keyed to (its package.json dependencies) is kit-managed (ruflo, @claude-flow/cli, agentic-qe) — an env we can't fully judge is exempt (so pnpm/typescript/context7/… envs are never touched)
  • each has an installed global baseline to compare against — @claude-flow/cli resolves from its nested location under ruflo (the same layout fact behind the fix(statusline): resolve the real CLI bins so delegation stops falling to stale npx #28 bin fix; there is never a top-level global copy)
  • at least one cached copy is strictly older than its baseline — equal-or-newer stays, since a current cache is exactly what a pre-install machine's npx fallback runs

Envs are pure caches; npx re-fetches on demand, so removal is always recoverable.

Wiring

  • ak status: new npx row (warn + fix when stale, listing pkg@version)
  • ak sync: prunes on the npx row or after versions upgrades — an upgrade is precisely what turns a previously-current cache stale, closing the gap in one pass
  • paths.mjs: npxCacheDir() resolved from npm_config_cache or platform defaults without spawning npm; a custom userconfig cache path is missed, which only means an empty scan (documented)

Verification

End-to-end against the real cache with a synthetic stale env planted (ruflo@3.21.1, the exact shape pruned by hand in #28):

⚠ npx  1 stale npx env(s) serve outdated code (ruflo@3.21.1)  → sync prunes them
• [npx] sync prunes them (npx re-fetches on demand) — because: …   (dry-run plan)
{"ok":true,"detail":"pruned 1 env(s): ruflo@3.21.1 (npx re-fetches on demand)"}
✓ npx  npx cache holds no stale ruflo-family envs

Current and foreign envs untouched throughout.

Tests

tests/kit/npx.test.mjs — 10 hermetic tests (synthetic _npx fixtures + injected baseline; never the machine's real cache): strictly-older flagged; equal kept; newer kept; mixed managed+unmanaged exempt (partial verdicts never prune); no-baseline exempt; unreadable copy exempt; missing cache dir returns empty; prune removes exactly the stale envs; clean-cache no-op; nested @claude-flow/cli baseline resolution.

Gates: 110 mjs + 43 cjs tests pass, eslint 0, typecheck 0.

npx envs (<npm-cache>/_npx/<hash>/) are snapshots keyed by requested
spec: once `npx @claude-flow/cli` caches a version, --prefer-offline
serves that copy forever — upgrading the global install never touches
it. The statusline/hook npx fallbacks execute these verbatim, which is
how a machine running a fixed ruflo 3.32.2 kept rendering the fabricated
CVE counter from a cached 3.28.0 (#28): six such envs held ~6.4 GB of
retired code spanning ruflo 3.10-3.28 and agentic-qe 3.11.5.

New `npx` status row + sync heal automates what #28 remediated by hand.
The prune rule is conservative by construction — a miss fails safe as
"not pruned", never a wrong prune:

- every package the env is keyed to must be kit-managed (ruflo,
  @claude-flow/cli, agentic-qe); an env we can't fully judge is exempt
- each needs an installed global baseline (@claude-flow/cli resolves
  from its NESTED location under ruflo — the same layout fact behind
  the #28 bin fix; there is never a top-level global copy)
- only a cached copy STRICTLY older than its baseline counts; equal or
  newer stays, since a current cache is what a pre-install machine's
  npx fallback runs

Runs on the `npx` row or after `versions` upgrades — an upgrade is
precisely what turns a previously-current cache stale. The cache dir is
resolved from npm_config_cache or platform defaults without spawning
npm; a custom userconfig cache path is missed, which only means an
empty scan.

Verified end-to-end with a synthetic stale env planted in the real
cache: status detects (warn row), sync --dry-run plans it, the heal
prunes exactly that env (current and foreign envs untouched), and
status converges to ok.
@pacphi
pacphi merged commit 035e07a into main Jul 17, 2026
11 checks passed
@pacphi
pacphi deleted the feat/sync-prunes-stale-npx-envs branch July 17, 2026 15:01
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