How Claude Code's Package Manager Detection System Works: 7-Step Priority Hierarchy Explained
Claude Code detects which JavaScript package manager to use through a cascading 7-step priority system defined in scripts/lib/package-manager.js, checking environment variables, config files, lock files, and installed binaries before defaulting to npm.
The everything-claude-code repository implements a robust package manager detection system that automatically identifies whether a project uses npm, pnpm, yarn, or bun. This logic ensures Claude Code executes the correct commands for installing dependencies, running scripts, and executing binaries—regardless of how a project is configured.
The 7-Step Detection Hierarchy
The getPackageManager() function in scripts/lib/package-manager.js (lines 57-136) evaluates sources in strict priority order:
1. Environment Variable Override
The system first checks for CLAUDE_PACKAGE_MANAGER. If set to a supported manager, this value wins immediately.
// Force pnpm regardless of project files
export CLAUDE_PACKAGE_MANAGER=pnpm
This override allows CI pipelines and power users to bypass all automatic detection (lines 60-68).
2. Project-Specific Config File
Next, the detector looks for .claude/package-manager.json in the project root. This JSON file can pin a manager for that specific codebase.
{
"packageManager": "yarn"
}
The setProjectPackageManager(pmName, projectDir) helper persists this config (lines 70-86).
3. package.json packageManager Field
The detector reads the standard packageManager field from package.json, respecting the npm specification. Values like "pnpm@8.6.0" are parsed to extract just the manager name before the @ character (lines 108-125).
4. Lock-File Detection
When no explicit config exists, the system scans for lock files with this priority:
| Priority | Lock File | Manager |
|---|---|---|
| 1 | pnpm-lock.yaml |
pnpm |
| 2 | bun.lockb |
bun |
| 3 | yarn.lock |
yarn |
| 4 | package-lock.json |
npm |
This ordering reflects modern JavaScript ecosystem preferences (lines 92-101).
5. Global User Preference
The detector loads ~/.claude/package-manager.json to check for a user-wide default. The setPreferredPackageManager(pmName) function writes this file (lines 108-116).
6. Installed Binary Check
If no configuration indicates a preference, the system calls commandExists for each manager in priority order (['pnpm','bun','yarn','npm']) and returns the first one found on PATH (lines 118-128).
7. npm Fallback
When all else fails, the system defaults to npm (lines 130-135).
Core API Functions
The package manager module exports several utilities for consuming the detection results:
getPackageManager(options)
Returns a descriptor object with the selected manager's details:
const { getPackageManager } = require('./scripts/lib/package-manager');
const pm = getPackageManager();
// {
// name: 'pnpm',
// config: { lockFile: 'pnpm-lock.yaml', installCmd: 'install', ... },
// source: 'lock-file'
// }
The source property indicates which detection step succeeded—useful for debugging.
getRunCommand(script, options)
Generates the correct command to run package scripts:
const { getRunCommand } = require('./scripts/lib/package-manager');
getRunCommand('dev'); // "pnpm dev"
getRunCommand('test'); // "pnpm test"
getRunCommand('install'); // "pnpm install"
getExecCommand(binary, args, options)
Builds commands to execute binaries through the detected manager:
const { getExecCommand } = require('./scripts/lib/package-manager');
getExecCommand('tsc', ['--noEmit']);
// "pnpm exec tsc --noEmit" or "npx tsc --noEmit" etc.
getCommandPattern(action)
Produces a regex matching all supported ways to invoke an action:
const { getCommandPattern } = require('./scripts/lib/package-manager');
const installPattern = getCommandPattern('install');
// /(?:npm install|pnpm install|yarn(?: install)?|bun install)/
This powers Claude's ability to recognize user intent across different manager syntaxes (lines 132-174).
Setting and Persisting Preferences
Global Preference
const { setPreferredPackageManager } = require('./scripts/lib/package-manager');
setPreferredPackageManager('yarn');
// Writes ~/.claude/package-manager.json
Project-Level Preference
const { setProjectPackageManager } = require('./scripts/lib/package-manager');
setProjectPackageManager('bun', '/path/to/project');
// Creates /path/to/project/.claude/package-manager.json
PACKAGE_MANAGERS Constant
Each supported manager is defined in the PACKAGE_MANAGERS constant (lines 13-53):
{
npm: {
lockFile: 'package-lock.json',
install: 'install',
run: 'run',
exec: 'exec',
defaultScripts: ['test', 'build', 'start', 'lint']
},
pnpm: {
lockFile: 'pnpm-lock.yaml',
install: 'install',
run: '',
exec: 'exec',
defaultScripts: ['dev', 'test', 'build', 'lint', 'format']
},
// ... yarn, bun
}
This central configuration drives command generation and lock-file detection throughout the codebase.
Integration Points
The detection system is invoked by:
scripts/hooks/session-start.js– Reports the detected manager at session startupscripts/setup-package-manager.js– Interactive CLI for viewing and changing preferences- Test suites –
tests/lib/package-manager.test.jsverifies each detection layer
Summary
- Seven-step priority hierarchy determines the active manager: environment variable → project config →
package.jsonfield → lock file → global config → installed binary → npm default scripts/lib/package-manager.jscontains all detection logic in ~175 lines- Four exported functions provide manager detection, command generation, and preference persistence
- Regex patterns enable cross-manager command recognition via
getCommandPattern() - Lock-file priority of
pnpm > bun > yarn > npmreflects modern tooling trends
Frequently Asked Questions
How do I force Claude Code to use a specific package manager?
Set the CLAUDE_PACKAGE_MANAGER environment variable to npm, pnpm, yarn, or bun. This overrides all automatic detection and project-level settings.
Where does Claude Code store my preferred package manager?
Global preferences live in ~/.claude/package-manager.json. Project-specific preferences are stored in .claude/package-manager.json relative to the project root.
Why does pnpm take priority over yarn in lock-file detection?
The priority list ['pnpm','bun','yarn','npm'] in scripts/lib/package-manager.js (line 92) reflects modern JavaScript ecosystem trends where pnpm and bun are increasingly preferred for their performance characteristics.
Can I use the package manager detection in my own scripts?
Yes. Require scripts/lib/package-manager.js and call getPackageManager() to receive the detected manager with its configuration and detection source.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →