pnpm/pnpm · error · PnpmError

INVALID_PACKAGE_NAME

INVALID_PACKAGE_NAME

Error message

Package in ${target} must have a name to get bin linked.

What it means

When linking bins for a package under node_modules, the linker accepts `bin` as an object (`{ 'cmd': 'file.js' }`) or as a string shorthand whose command name is derived from `manifest.name`. If `bin` is a string but the manifest has no `name`, the command name cannot be derived, so it throws INVALID_PACKAGE_NAME.

Source

Thrown at pnpm11/bins/linker/src/index.ts:220

  },
  target: string
): Promise<CommandInfo[]> {
  const manifest = opts.allowExoticManifests
    ? (await safeReadProjectManifestOnly(target) as DependencyManifest)
    : await safeReadPkgJson(target)

  if (manifest == null) {
    // There's a directory in node_modules without package.json: ${target}.
    // This used to be a warning but it didn't really cause any issues.
    return []
  }

  if (isEmpty(manifest.bin) && !await isFromModules(target)) {
    opts.warn(`Package in ${target} must have a non-empty bin field to get bin linked.`, 'EMPTY_BIN')
  }

  if (typeof manifest.bin === 'string' && !manifest.name) {
    throw new PnpmError('INVALID_PACKAGE_NAME', `Package in ${target} must have a name to get bin linked.`)
  }

  return getPackageBinsFromManifest(manifest, target)
}

async function getPackageBinsFromManifest (manifest: DependencyManifest, pkgDir: string): Promise<CommandInfo[]> {
  const cmds = await getBinsFromPackageManifest(manifest, pkgDir)
  let nodeExecPath: string | undefined
  if (manifest.engines?.runtime && runtimeHasNodeDownloaded(manifest.engines.runtime)) {
    const require = createRequire(import.meta.dirname)
    // Using Node.js’ resolution algorithm is the most reliable way to find the Node.js
    // package that comes from this CLI's dependencies, because the layout of node_modules can vary.
    // In an isolated layout, it will be located in the same node_modules directory as the CLI.
    // In a hoisted layout, it may be in one of the parent node_modules directories.
    const nodeDir = path.dirname(require.resolve('node/CHANGELOG.md', { paths: [pkgDir] }))
    if (nodeDir) {
      nodeExecPath = path.join(nodeDir, IS_WINDOWS ? 'node.exe' : 'bin/node')
    }

View on GitHub (pinned to 5b11d3a15b)

Solutions

  1. Add `"name": "your-pkg"` to the package.json at the path shown in the error
  2. Or switch `bin` to the explicit object form: `"bin": { "your-cmd": "./cli.js" }`, which does not depend on `name`
  3. Re-run the install/link after saving the manifest

Example fix

// before (package.json)
{
  "version": "0.1.0",
  "bin": "./cli.js"
}

// after
{
  "name": "my-cli",
  "version": "0.1.0",
  "bin": "./cli.js"
}
// alternative: "bin": { "my-cli": "./cli.js" }
Defensive patterns

Strategy: type-guard

Validate before calling

const manifest = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'))
if (typeof manifest.bin === 'string' && !manifest.name) {
  throw new Error(`${pkgJsonPath}: string "bin" requires a "name" — fix before install`)
}

Type guard

interface BinManifest { name?: string, bin?: unknown }

function hasLinkableBin (m: BinManifest): boolean {
  if (m.bin == null) return false
  if (typeof m.bin === 'object') return Object.keys(m.bin).length > 0
  // string shorthand: only linkable when a name exists to derive the command from
  return typeof m.name === 'string' && m.name.length > 0
}

Prevention

When it happens

Trigger: `pnpm install`/`pnpm link` processing a dependency (often a git dep, `file:` dep, or symlinked local package) whose package.json has `"bin": "./cli.js"` but no `name` field.

Common situations: Local scaffolding packages or test fixtures written without a name; generated manifests; git dependencies with minimal package.json; linking a work-in-progress package before its manifest is finalized.

Related errors


AI-assisted analysis of pnpm/pnpm@5b11d3a15b (2026-08-16). Data as JSON: /api/errors/47a422b3b56b6911. Report an issue: GitHub.