pnpm/pnpm · error · PnpmError

PACK_APP_INVALID_TARGET

PACK_APP_INVALID_TARGET

Error message

Invalid target: "${raw}". Expected format: <os>-<arch>[-<libc>] where <os> is ${SUPPORTED_OS.join('|')}, <arch> is x64|arm64, optional <libc> is musl (linux only).

What it means

pack-app --target takes a strict triplet `<os>-<arch>[-<libc>]` validated by the anchored TARGET_PATTERN /^(linux|darwin|win32)-(x64|arm64)(?:-(musl))?$/. Anything else — different OS names (freebsd, sunos), arch names (x86_64, aarch64, i686, armv7), wrong separators, extra segments, or traversal payloads like `linux-x64-musl-../../outside` — fails to match and is rejected immediately. The strictness is deliberate: the raw string later flows into path.join for the output directory, so unanchored parsing would allow escaping the output directory.

Source

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

  const nodeMirrorBaseUrl = getNodeMirror(nodeDownloadMirrors, releaseChannel)
  const version = await resolveNodeVersion(fetch, versionSpecifier, nodeMirrorBaseUrl)
  if (!version) {
    throw new PnpmError('PACK_APP_NODE_VERSION_NOT_FOUND',
      `Could not find a Node.js version that satisfies "${specifier}"`)
  }
  return version
}

// Parsed triplet must match this shape exactly. We anchor and constrain each
// segment so that inputs like `linux-x64-musl-../../outside` are rejected
// outright — otherwise `target.raw` would later flow into path.join for the
// output directory and could escape it.
const TARGET_PATTERN = /^(linux|darwin|win32)-(x64|arm64)(?:-(musl))?$/

function parseTarget (raw: string): ParsedTarget {
  const match = TARGET_PATTERN.exec(raw)
  if (!match) {
    throw new PnpmError('PACK_APP_INVALID_TARGET',
      `Invalid target: "${raw}". Expected format: <os>-<arch>[-<libc>] where <os> is ${SUPPORTED_OS.join('|')}, <arch> is x64|arm64, optional <libc> is musl (linux only).`)
  }
  const [, platform, arch, libc] = match
  if (libc === 'musl' && platform !== 'linux') {
    throw new PnpmError('PACK_APP_INVALID_TARGET',
      `The "musl" libc suffix is only valid for linux targets (got "${raw}").`)
  }
  return { raw, platform, arch, libc: libc || undefined }
}

// 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)@(.+)$/

View on GitHub (pinned to 6261b7f388)

Solutions

  1. Use exactly one of linux|darwin|win32 for the OS and x64|arm64 for the arch, e.g. `--target linux-x64`, `--target darwin-arm64`, `--target win32-x64`.
  2. Add the optional `-musl` suffix only for Linux Alpine-style targets: `--target linux-x64-musl`.
  3. If you generate targets in a script, normalize `x86_64`→x64, `aarch64`/`arm64e`→arm64, `windows`→win32, `macos`/`osx`→darwin before invoking pack-app.

Example fix

# before
pnpm pack-app --target x86_64-unknown-linux-gnu

# after
pnpm pack-app --target linux-x64
# or for Alpine
pnpm pack-app --target linux-x64-musl
Defensive patterns

Strategy: validation

Validate before calling

const TARGET_PATTERN = /^(linux|darwin|win32)-(x64|arm64)(?:-(musl))?$/
function isValidTarget(raw: string): boolean {
  return TARGET_PATTERN.test(raw)
}

const NORMALIZE: Record<string, string> = {
  x86_64: 'x64', aarch64: 'arm64', arm64e: 'arm64',
  windows: 'win32', macos: 'darwin', osx: 'darwin',
}
function normalizeTarget(raw: string): string {
  let [os, arch, libc] = raw.split('-')
  os = NORMALIZE[os] ?? os
  arch = NORMALIZE[arch] ?? arch
  return libc ? `${os}-${arch}-${libc}` : `${os}-${arch}`
}

Type guard

function isPackAppTarget(raw: string): boolean {
  return /^(linux|darwin|win32)-(x64|arm64)(?:-(musl))?$/.test(raw)
}

Try / catch

try {
  await runPackApp({ ...opts, targets })
} catch (err) {
  if ((err as any)?.code === 'PACK_APP_INVALID_TARGET') {
    targets = targets.map(normalizeTarget).filter(isPackAppTarget)
    await runPackApp({ ...opts, targets })
  } else throw err
}

Prevention

When it happens

Trigger: Passing `--target x86_64-linux` (uname-style, wrong order and name), `--target linux-armv7l`, `--target darwin-x64-gnu`, `--target Linux-x64` (uppercase), or any string with path segments. Any of these produces this error before any network or build work happens.

Common situations: Copy-pasting targets from Docker/Rust/Go toolchains that use `x86_64-unknown-linux-gnu` style triplets; assuming pnpm accepts uname(1) output; scripting pack-app from `process.platform`-adjacent values that use different naming (e.g. 'win32' vs 'windows').

Related errors


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