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
- 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`.
- Add the optional `-musl` suffix only for Linux Alpine-style targets: `--target linux-x64-musl`.
- 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
- Never pass uname(1) or Rust/Go triplets through unchecked — normalize x86_64/aarch64 to x64/arm64 first.
- Validate targets against the anchored regex in your build script before invoking pnpm.
- Build target lists from a fixed allowlist (6 Linux/macOS/Windows combos plus linux musl variants), not from user input.
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
- PACK_APP_INVALID_RUNTIME
- PACK_APP_INVALID_OUTPUT_NAME
- PACK_APP_MACOS_SIGN_FAILED
- PACK_APP_MACOS_SIGN_UNSUPPORTED_HOST
- PACK_APP_MISSING_ENTRY
AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17).
Data as JSON: /api/errors/758b04c57e3fda9e.
Report an issue: GitHub.