Skip to content

fix(install): skip npx's cached copy when finding the active ccstatusline - #749

Merged
sirmalloc merged 1 commit into
sirmalloc:mainfrom
eric-engberg:fix/npx-active-binary
Oct 29, 2026
Merged

sirmalloc merged 1 commit into
sirmalloc:mainfrom
eric-engberg:fix/npx-active-binary

Conversation

@eric-engberg

Copy link
Copy Markdown
Contributor

What

Launching the TUI with npx -y ccstatusline@latest next to a pinned global install no longer takes npx's cached copy for the active ccstatusline. Install Complete and Manage Installation now report the real global install and its package manager, with no false "Multiple ccstatusline binaries are on PATH" warning. The pinned-version guard fires again when the npx-launched TUI is newer than the pinned install.

Why (evidence)

The README's route to a pinned install is to launch the TUI through npx -y ccstatusline@latest. While npx runs it, npm prepends the bin dir of its cached copy to PATH: libnpmexec installs into resolve(npxCache, hash) (npxCache = join(cache, '_npx'), hash = first 16 hex characters of a sha512 of the package spec) and passes <installDir>/node_modules/.bin as the first entry to @npmcli/run-script's setPATH (checked against npm 11.19.1). So which -a ccstatusline / where ccstatusline lists ~/.npm/_npx/<hash>/node_modules/.bin/ccstatusline (Windows: %LocalAppData%\npm-cache\_npx\<hash>\node_modules\.bin\ccstatusline.cmd/.ps1) ahead of the global install.

getPersistentCommandResolutionPaths already dropped bunx's temporary bunx-*ccstatusline@* copy, but not npx's, so resolvedPaths[0] was the npx copy. Reproduced with a fake npx cache bin first on PATH and an npm global prefix after it:

  • inspectActiveGlobalCommand returned packageManager: 'unknown', version: null and "⚠ Multiple ccstatusline binaries are on PATH. Claude Code will run the first match: …/_npx/9f3c1e2a7b6d4c80/node_modules/.bin/ccstatusline". Claude Code never runs that copy; it only exists on the PATH npx hands the TUI.
  • inspectGlobalCommandResolution (Install Complete) gave the same warning.
  • Since the manager came back unknown, getPinnedVersionMismatch returned null, so a newer TUI launched through npx opened the Main Menu and saved settings the older pinned install may not support.

How

isTransientStatusLinePath (was isTransientBunxStatusLinePath) in src/utils/global-command-resolution.ts checks a list of patterns: the existing bunx one, plus _npx/<hash>/node_modules/.bin/ccstatusline, optionally with .cmd/.ps1, either path separator, case-insensitive. Only the part before _npx depends on where the npm cache lives (npm_config_cache), so a custom cache matches too. All three consumers (Install Complete's warning, Manage Installation, the pinned-version guard) go through this filter, so nothing else changes.

Other one-off runners (npm 6's npx, pnpm dlx, yarn dlx) use different temp layouts and aren't in the README, so they're left as they are.

Demo

which -a ccstatusline lists npx's cached copy first, then a pinned npm global install, and then the TUI launches. Fake bins and a stub npm (answering prefix -g/root -g) in a scratch HOME.

Manage Installation: before, it takes the npx copy for the active binary, with no manager or version and a false "Multiple binaries" warning. After, it shows the npm global install.

Powerline

Before:

Manage Installation under npx before the fix, Powerline

After:

Manage Installation under npx after the fix, Powerline

Plain

Before:

Manage Installation under npx before the fix, plain

After:

Manage Installation under npx after the fix, plain

Pinned v2.2.30, TUI v2.2.32: before, the TUI opens the Main Menu and ctrl+s saves. After, it opens on the Pinned Install Version Mismatch guard, which ignores ctrl+s.

Powerline

Before:

Pinned-version guard under npx before the fix, Powerline

After:

Pinned-version guard under npx after the fix, Powerline

Plain

Before:

Pinned-version guard under npx before the fix, plain

After:

Pinned-version guard under npx after the fix, plain

Testing

  • 6 new tests, all failing on main before the fix:
    • global-command-resolution: POSIX ~/.npm/_npx, a custom npm cache dir, and the Windows %LocalAppData%\npm-cache\_npx shims (extensionless/.cmd/.ps1) are dropped and the global path kept; the existing bunx test still passes.
    • global-package-manager: POSIX and Windows npx-first PATHs resolve to npm with the global version and no warning.
    • App: launched with an npx-first PATH and an older pinned npm install, the TUI opens on Pinned Install Version Mismatch.
  • bun test: 3174 pass, 0 fail across 178 files (main: 3168).
  • bun run lint: clean.
  • Changed test files under Node vitest: 28 pass / 20 fail (main: 28 / 14). Every failure, old and new, is the existing "Cannot spy on export execFileSync/execFile" ESM error on node:child_process.
  • Built dist under Bun 1.4.2 and Node v26.10.0: plain and Powerline piped renders agree across runtimes and match main; the TUI opens and exits cleanly under both.

…line

While npx runs the TUI (the README's route to a pinned install), npm puts
the bin dir of its cached copy, <npm cache>/_npx/<hash>/node_modules/.bin,
first on PATH. The PATH lookup behind Install Complete, Manage
Installation and the pinned-version guard skipped bunx's temporary copy
but not npx's, so under npx it took the cached copy for the active
binary:

- Install Complete and Manage Installation warned that multiple
  ccstatusline binaries are on PATH and that Claude Code would run the
  npx copy, which isn't on Claude Code's PATH at all
- Manage Installation showed the pinned install without its package
  manager or version
- the pinned-version guard never fired, so a newer TUI launched through
  npx could save settings the older pinned install may not support

npx cache bins are now skipped the same way as bunx's: ccstatusline, or
its .cmd/.ps1 shim, in node_modules/.bin under an _npx/<hash> directory,
with either path separator. A custom npm cache (npm_config_cache) only
moves the part before _npx, so it's covered too.
@sirmalloc
sirmalloc merged commit 7d0bd50 into sirmalloc:main Oct 29, 2026
3 checks passed
axisrow added a commit to axisrow/ccstatusline that referenced this pull request Oct 29, 2026
sirmalloc#755 live picker preview, sirmalloc#754 fast mode widget, sirmalloc#753 thinking-effort test stub, sirmalloc#752 transcript block fallback fixes, sirmalloc#751 editor keys no longer pause on global install lookup, sirmalloc#750 git cache TTL starts after the run, sirmalloc#749 npx cache skip, sirmalloc#747 Block Usage hides at 100% and swaps with Block Reset Timer)

Conflict policy: upstream infra wins, fork features ported on top. Joins:
sirmalloc#750 stamps the git cache after the run while keeping the fork's execGit
hardening/env snapshot and daemon prefetch bookkeeping; sirmalloc#752's
isApiErrorMessage guard lands in the fork's shared collectTimestampFromLine
(sync + async twins), upstream's duplicate getActivityTimestamp dropped;
sirmalloc#747 adds SESSION_AT_LIMIT alongside the fork's zero hide state
(BlockResetTimer keeps zero + under-limit via isAtSessionLimit, SessionUsage
is no-data/zero/at-limit); sirmalloc#755 replaces the fork's picker window + "Showing
X-Y of N" note with upstream's renderWindowedList/getPickerMaxVisible and
live preview (markers ↑/↓ N more cover the counts); sirmalloc#751's installationState
useMemo replaces the fork's inline lookups, the fork's mainMenuIndex helper
is kept.

Co-Authored-By: Claude Code <noreply@anthropic.com>
pcvelz added a commit to pcvelz/ccstatusline-usage that referenced this pull request Oct 29, 2026
Upstream: Extra Usage Limit widget (sirmalloc#756), live widget preview while picking (sirmalloc#755), Fast Mode widget (sirmalloc#754), transcript block fallback fixes (sirmalloc#752), editor keys on a global install (sirmalloc#751), git cache TTL (sirmalloc#750), npx cached copy skip (sirmalloc#749), Block Usage hide at 100% (sirmalloc#747), Block Reset Timer hide until 100% (sirmalloc#697), fish-style branch abbreviation (sirmalloc#614), jj detached HEAD (sirmalloc#705), hooks save warning (sirmalloc#703), dev reload (sirmalloc#700), merge ends at a hidden widget (sirmalloc#698), Block Forecast and Block Limit Timer widgets (sirmalloc#699), contributor guide (sirmalloc#706), POSIX path case fix (sirmalloc#707), whitespace-distinct conflict filenames (sirmalloc#709), Quick Start reorder (sirmalloc#715), bunx launch guidance (sirmalloc#714), jj diff summary counts (sirmalloc#711), powerline manual separators (sirmalloc#713), keychain credential by account (sirmalloc#532), Keychain probe timeout (sirmalloc#760), usage cache scoped per CLAUDE_CONFIG_DIR (sirmalloc#266), test isolation (sirmalloc#717, sirmalloc#753), dev-dependency bumps (sirmalloc#745)

Conflicts resolved per the standard-resolution table; fork deletions re-applied.

Hand edits outside conflicts:
- src/utils/global-command-resolution.ts: fork-rename (ccstatusline@ -> ccstatusline-usage@ in upstream's new bunx pattern)
- src/utils/__tests__/global-package-manager.test.ts: fork-rename (package dir ccstatusline-usage in upstream's new tests)
- src/utils/usage-fetch.ts: compat fix - cacheUsageData gained a fetchedAtMs arg (upstream sirmalloc#266); adapted the fork-only fetchFreshUsageData call site
- src/utils/__tests__/usage-fetch.test.ts: compat fixes - cache/lock now hashed per CLAUDE_CONFIG_DIR (usageFilePath helper) in four fork tests; upstream fableUsage renamed to fork's weeklyFableUsage; seedUsageCache takes TokenHome
- src/utils/__tests__/usage-prefetch.test.ts: compat fix - mockFetchUsageData renamed to upstream's mockFetchUsage in cleanly-merged tests
- README.md: took upstream's Quick Start move (sirmalloc#715) and dropped the fork's old copy; fork-rename of its ccstatusline@ specifiers; links to fork-deleted docs (docs/USAGE.md, docs/WINDOWS.md) replaced with the fork's inline text
- CONTRIBUTING.md: links to the fork-deleted docs/DEVELOPMENT.md pointed at the README Development section
- docs/test-retirements.md: ledger entries for the 12 upstream test renames (sirmalloc#697, sirmalloc#698, sirmalloc#700, sirmalloc#705, sirmalloc#713, sirmalloc#760)
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.

2 participants