pnpm/pnpm · error · PnpmError

INVALID_JSR_SPECIFIER

INVALID_JSR_SPECIFIER

Error message

JSR specifier '${rawSpecifier}' is missing a package name

What it means

Thrown by parseJsrSpecifier when a `jsr:` dependency specifier carries no package name and no alias to derive one from. JSR packages are always scoped (`@scope/name`), so after stripping the `jsr:` prefix the parser needs either a leading `@scope/name` or an alias; with neither, there is nothing to resolve. The remainder of the specifier is treated as a version selector, which is why a nameless specifier is rejected rather than guessed.

Source

Thrown at pnpm11/resolving/jsr-specifier-parser/src/index.ts:43

      }
    }

    // syntax: jsr:@<scope>/<name>@<version_selector>
    const jsrPkgName = rawSpecifier.substring(0, index)
    return {
      jsrPkgName,
      npmPkgName: jsrToNpmPackageName(jsrPkgName),
      versionSelector: rawSpecifier.substring(index + '@'.length),
    }
  }

  // syntax: jsr:<name>@<version_selector> (invalid)
  if (rawSpecifier.includes('@')) {
    throw new PnpmError('MISSING_JSR_PACKAGE_SCOPE', 'Package names from JSR must have a scope')
  }

  if (!alias) {
    throw new PnpmError('INVALID_JSR_SPECIFIER', `JSR specifier '${rawSpecifier}' is missing a package name`)
  }

  // syntax: jsr:<spec>
  return {
    versionSelector: rawSpecifier,
    jsrPkgName: alias,
    npmPkgName: jsrToNpmPackageName(alias),
  }
}

function jsrToNpmPackageName (jsrPkgName: string): string {
  if (jsrPkgName[0] !== '@') {
    throw new PnpmError('MISSING_JSR_PACKAGE_SCOPE', 'Package names from JSR must have a scope')
  }
  // The returned name is used in registry URLs and metadata cache file paths,
  // so anything that is not a valid npm package name must never make it through.
  if (!validateNpmPackageName(jsrPkgName).validForOldPackages) {
    throw new PnpmError('INVALID_JSR_PACKAGE_NAME', `The package name '${jsrPkgName}' is invalid`)

View on GitHub (pinned to 6261b7f388)

Solutions

  1. Rewrite the dependency with the full scoped JSR name: "@scope/pkg": "jsr:@scope/pkg" (optionally with a selector: "jsr:@scope/pkg@^1.0.0")
  2. Look the package up on jsr.io and copy its exact `@scope/name` verbatim
  3. If you intended an alias install, put the scoped name in the specifier itself rather than relying on the dependency key

Example fix

// before (package.json)
"foo": "jsr:foo"

// after
"@scope/foo": "jsr:@scope/foo@^1.0.0"
Defensive patterns

Strategy: validation

Validate before calling

const JSR_SPEC = /^jsr:@[a-z0-9-]+\/[a-z0-9-._]+(@.+)?$/
export function assertValidJsrDependencies (deps: Record<string, string> = {}) {
  for (const [name, spec] of Object.entries(deps)) {
    if (!spec.startsWith('jsr:')) continue
    if (!JSR_SPEC.test(spec)) {
      throw new Error(`Invalid JSR specifier "${spec}" for "${name}"; expected jsr:@scope/name[@selector]`)
    }
  }
}
// run against manifest.dependencies/devDependencies before pnpm install

Prevention

When it happens

Trigger: parseJsrSpecifier is invoked with a raw specifier that starts with `jsr:`, the remainder does not start with `@`, contains no `@` (so it cannot be a name@selector pair), and the optional alias argument is undefined. Concrete shapes: dependency entries like `"foo": "jsr:foo"` or `"foo": "jsr:"` where the scoped name was never typed.

Common situations: Hand-editing package.json and forgetting the `@scope/` part; copying a Deno-style import specifier (`jsr:foo`) into package.json; code generators or codemods that emit the protocol prefix but drop the scoped package name.

Related errors


AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17). Data as JSON: /api/errors/e0f27011f4968956. Report an issue: GitHub.