pnpm/pnpm · error · PnpmError

PACK_APP_INVALID_RUNTIME

PACK_APP_INVALID_RUNTIME

Error message

Invalid runtime "${spec}". Expected format: <name>@<version> (supported runtimes: ${SUPPORTED_RUNTIMES.join(', ')}; e.g. "node@${MIN_BUILDER_VERSION.major}.${MIN_BUILDER_VERSION.minor}.0").

What it means

The runtime spec must match /^(node)@(.+)$/ — a runtime name, an @, and a version. Only the name "node" is supported today; the name@version prefix is kept so future runtimes (bun, deno) can reuse the flag without a breaking change. Parsing the name also avoids pnpm's global `node-version` rc setting leaking into Config['nodeVersion'] and shadowing `pnpm.app.runtime`. A bare version ("25.5.0"), a missing version ("node@"), "node" alone, or another runtime name ("bun@1") all fail the pattern.

Source

Thrown at pnpm11/releasing/commands/src/pack-app/packApp.ts:466

// Runtime spec is "<name>@<version>". Only "node" is supported today; the
// prefix is kept so future runtimes (bun, deno) can share the same flag
// without a breaking change. Reading the runtime name rather than a bare
// version also avoids shadowing pnpm's global `node-version` rc setting,
// whose value would otherwise leak into Config['nodeVersion'] and override
// `pnpm.app.runtime`.
const SUPPORTED_RUNTIMES = ['node'] as const
const RUNTIME_PATTERN = /^(node)@(.+)$/

interface ParsedRuntime {
  name: typeof SUPPORTED_RUNTIMES[number]
  version: string
}

function parseRuntime (spec: string): ParsedRuntime {
  const match = RUNTIME_PATTERN.exec(spec)
  if (!match) {
    throw new PnpmError('PACK_APP_INVALID_RUNTIME',
      `Invalid runtime "${spec}". Expected format: <name>@<version> (supported runtimes: ${SUPPORTED_RUNTIMES.join(', ')}; e.g. "node@${MIN_BUILDER_VERSION.major}.${MIN_BUILDER_VERSION.minor}.0").`)
  }
  return { name: match[1] as ParsedRuntime['name'], version: match[2] }
}

// Characters that Win32 rejects in filenames, plus NUL. Path separators are
// checked separately via `path.basename` so the message is crisp.
const INVALID_FILENAME_CHARS = /[<>:"|?*\0]/
// Win32 reserved device names (case-insensitive, with or without an extension).
const RESERVED_WINDOWS_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i

// Reject anything that would let the output escape its target directory, or
// that would fail filesystem-level validation on any supported host. This
// surfaces problems at `pack-app` invocation time instead of letting them
// blow up later in `writeFile(outputFile, …)`.
function validateOutputName (name: string): string {
  if (
    name !== path.basename(name) ||

View on GitHub (pinned to 6261b7f388)

Solutions

  1. Write the spec as name@version: `--runtime node@25.5.0` (a major-only form like node@25 also matches the pattern).
  2. In package.json: `"pnpm": { "app": { "runtime": "node@25.5.0" } }`.
  3. If you intended another runtime, know that only node is supported today — build a plain SEA with that runtime's own tooling instead.

Example fix

# before
pnpm pack-app --runtime 25.5.0

# after
pnpm pack-app --runtime node@25.5.0

# package.json
"pnpm": { "app": { "runtime": "node@25.5.0" } }
Defensive patterns

Strategy: validation

Validate before calling

const RUNTIME_PATTERN = /^(node)@(.+)$/
function isValidRuntimeSpec(spec: string): boolean {
  return RUNTIME_PATTERN.test(spec)
}

function toRuntimeSpec(v: string): string {
  return v.includes('@') ? v : `node@${v}` // repair bare versions
}

Type guard

function isNodeRuntimeSpec(spec: string): spec is `node@${string}` {
  return /^node@(.+)$/.test(spec)
}

Try / catch

try {
  await runPackApp(opts)
} catch (err) {
  if ((err as any)?.code === 'PACK_APP_INVALID_RUNTIME') {
    await runPackApp({ ...opts, runtime: 'node@25.5.0' })
  } else throw err
}

Prevention

When it happens

Trigger: `--runtime 25.5.0` (bare version, no name), `--runtime node` (no @version), `--runtime bun@1.0.0` or `--runtime deno@2` (unsupported name), `"pnpm.app.runtime": "v25.5.0"` with a leading v and no name. Note this error fires on the *shape*, before any version resolution — a well-formed node@<old-version> instead raises PACK_APP_RUNTIME_TOO_OLD.

Common situations: Users assuming --runtime takes the same value as `pnpm env use` (bare versions); copying `engines.node` values into pnpm.app.runtime; early adopters trying bun/deno SEAs after seeing the flag.

Related errors


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